前言
由于实际的生产环境下是不太可能出现单服务情况,往往都是zk或其他注册中心组成的集群环境,使得服务高可用更加健壮。
对于dubbo来说,抛开比较细节的东西,集群容错比较主要的流程是:
- 获取服务列表
- 路由
- 进行负载均衡
- 错误处理
对应三个不同的接口:
++Directory、Router、LoadBalance、Cluster++
上一篇文章已经介绍了负载均衡,这篇文章介绍Directory和Cluster的实现,Router放到后面文章再解释(不是关键的)
正文
Directory
public interface Directory<T> extends Node {
Class<T> getInterface();
List<Invoker<T>> list(Invocation invocation) throws RpcException;
}
Directory接口有两个方法,其中list为主要的方法,传入需要调用的方法以及参数等组合的invocation,获取一个可以被调用的list列表
Directory有一个抽象类AbstractDirectory,使用了模板方法设计模式,实现了list方法,子类重写doList方法并且调用它
调用子类的doList方法,然后使用Router过滤
AbstractDirectory有两个实现类:StaticDirectory和RegistryDirectory
- StaticDirectory:静态列表,通过构造方法传入一个Invoker的集合后不再改变,doList仅仅返回这个集合
- RegistryDirectory:通过注册中心获取的动态Invoker列表
RegistryDirectory
RegistryDirectory在继承了AbstractDirectory,同时实现了NotifyListener,表示自己可以注册到zookeeper等注册中心,并且可以通过notify方法获取动态跟新 构造方法:
将url的信息解析成map,将method提取出来
调用registry的subscribe,将自己作为listener注册,注册成功后registry会调用notify方法通知
将回掉的urls分类后进行不同的处理,最后调用refreshInvoker方法刷新本地缓存可调用列表
refreshInvoker大致分为三步:
- 将原来的urlInvokerMap保存起来,将新传入的url放入缓存,为的是将新的url和旧的做比较,删除那些已经失效的服务
- 将url转换为invoker的map,如果以前已经存在过map,则不会重新引用,具体逻辑:
-
首先判断协议是否匹配
用获取的url匹配原始订阅URL中支持的协议,并且还要用SPI的方法去检查是否有该协议的扩展,如果不支持就忽略该url
-
获取invoker
从原来的map获取inovker,如果没有就使用Protocol的refer方法,去获取一个invoker实例
-
然后将获取到的string-invoker的map转换为method-List的映射,具体就是循环解析url,将url方法信息解析出来并且聚合
- 对比以前的map和新的map,将没有使用到的invoker给销毁,调用invoker的destroy方法
到此为止,RegistryDirectory内部已经保存了第一次从注册中心获取到的信息,并且转换成了方法-invoker的映射,doList方法不过也就此基础上进行筛选
根据需要远程调用服务的信息,获取List的方法
还记得之前RegistryProtocol的DoRefer吗?
这里new了一个RegistryDirectory,并且调用了subscribe方法,在subscribe方法调用完成后就会触发notify,RegistryDirectory内部就已经有了方法的map。
随后就是调用黄线的cluster.join方法,Directory的list方法和laodBalance的select都是在Cluster内部完成,目前已经成功获取到了可用的inovker列表以及负载均衡,接下来就是容错的策略
LoadBalance
Cluster
接口:
@SPI(FailoverCluster.NAME)
public interface Cluster {
@Adaptive
<T> Invoker<T> join(Directory<T> directory) throws RpcException;
}
join方法传入一个Directory,返回一个Invoker。Cluster一共有9种实现,实现类要么继承AbstractClusterInvoker,或者是直接实现Invoker接口,可以看作是将Invoker接口给包装,在invoke方法内实现负载均衡和容错。
AbstractClusterInvoker
AbstractClusterInvoker是Invoker的实现类,将负载均衡方法给抽离,还是用了模板方法,将具体实现交给子类的doInvoke
select首先对sticky属性进行了判断,sticky参数的含义是尽量让客户端向同一个服务提供者发起调用请求,如果包含了这个属性且服务是可用的,就不用进行负载均衡直接返回,不然就调用doSelect进行选择
selected参数表示这个list内的invoker是已经选择过了,这次调用是不可选的,当调用loadbalance选择出一个invoker后,需要判断是不是包含在这个selected列表中,如果配置了可用性检查还需要检查available属性;如果命中就需要重新选择,重新选择后也有可能选择出一个null的结果,那么就按照list的index向下顺延
reselect方法:
reselect方法有两个阶段:
- availablecheck表示检查isAvailable属性,首先遍历invokers列表,找出那些符合选择的invoker(不在selected中),添加到reselectInvokers的list,调用loadbalance方法选举
- 如果没有符合选择的条件,就从selected中选择isAvailable为true的再选择,还是没有就只能返回null了
可以发现在selected中的invoker也不一定就是被排除的,也有可能被选择到
以上是AbstractClusterInvoker中关于负载均衡的方法,如需使用尽在子类实现的doInvoke中调用
这是AbstractClusterInvoker的invoke方法,主要是调用list方法获取invoker列表,list方法底层由Directory.list实现
具体实现
Cluster一共有9种容错机制,其中有7中普通和2种特殊,见下表:
名称 | 简介 |
---|---|
AvailableCluster | 最简单的机制,遍历出一个可用的invoker,如果找不到抛出异常 |
BroadcastCluster | 广播所有可用的服务,任意一个服务报错就记录报错,最后抛出异常,由于是广播不需要负载均衡 |
FailbackCluster | 请求失败后会记录一个失败队列,由定时线程去重试,请求会负载均衡 |
FailfastCluster | 快速失败,请求会负载均衡,失败后快速抛出异常不重试 |
FailoverCluster | 重试机制,按照配置的重试次数重试,会负载均衡 |
FailsafeCluster | 出现异常直接忽略,不关心是否调用成功,会负载均衡 |
ForkingCluster | 同时调用多个服务,只要一个返回就返回,会进行多次负载均衡 |
MergeableCluster | 特殊机制,多个节点的请求结果合并 |
MockCluster | 特殊机制,请求失败的时候返回一个伪造或自定义的结果 |
- AvailableCluster
遍历获取isAvailable为true的第一个,找不到就抛出异常
- BroadcastCluster
遍历服务依此调用,任意一个服务报错后,在所有服务调用完毕后抛出异常,由于是广播不需要负载均衡,一般用于跟新服务状态
- FailbackCluster
在失败后调用addFailed方法,将invoker加入hashmap
addFailed内部会初始化定时器,时间为5000ms一次执行retryFailed(),主要执行failed.put(invocation, router),加入map中
retryFailed遍历map,将invoker执行,如果还是失败就记录日志
failback适用于一些异步或最终一致性的服务,
- FailfastCluster
进行了负载均衡,调用失败后直接返回错误,通常用于非幂等写操作
- FailoverCluster
首先从url中解析出重试次数,在第二次重试及以后,都要重新获取invoker列表,因为可能会发生变动,使用invoked来记录已重试过的invoker,每次重试都需要负载均衡
Failover是默认的容错方式,一般用户读操作或幂等性写操作,由于重试会重新获取服务列表和负载均衡,会增大接口的延迟和服务的压力
- FailsafeCluster
Failsafe直接忽略了异常,表示不关心返回,一般用于写日志或其他不重要的操作
- ForkCluster
按照配置的最大并行数forks的值去进行多次负载均衡,开启了forks个线程去处理每个invoker,执行完毕后放入BlockingQueue队列,一旦某一个线程返回正常就结束,当线程抛出异常后用一个原子变量记录错误的次数,一旦全部错误,则抛出异常
Fork策略可以提高远程调用的性能,但是也会浪费更多的资源
- MergeableCluster
Mergeable会按照自定义的策略,将多个invoker调用的结果给合并
开启多线程调用,结果使用Future接受,调用future.get会阻塞,这 里设置了超时时间默认1000ms,然后将结果放入resultList,后续按照规则解析
- MockCluster
由于Cluter是一个SPI方法,会自动套上Wrapper类,而MockClusterWrapper是Cluter的Wrapper,表示在执行其他Cluter策略的时候都会先经过Mock的逻辑
看MockClusterInvoker内部
会检查url中是否有mock字段,配置force:xxx则强制走mock流程,配置fail:xxx则远程调用失败后使用mock,进入doMockInvoke方法
进入MockInovker看看
这个mock的参数可以配置为return XXX,throw XXX,或者是一个实现类,这里就是在解析mock字段的参数,然后配置按照规则返回,可以返回null或者是具体的值,也可有抛出错误和用一个默认的实现类去代替。
比如配置:force:true或者force:defalut,会到interface目录下找xxMock结尾的实现类;也可以详细配置:force:com.alibaba.dubbo.demo.DemoServiceMock
以上就是cluster接口以及实现方法,具体实现并不难