Dubbo源码分析(6)—— 集群容错

160 阅读8分钟

前言

由于实际的生产环境下是不太可能出现单服务情况,往往都是zk或其他注册中心组成的集群环境,使得服务高可用更加健壮。

对于dubbo来说,抛开比较细节的东西,集群容错比较主要的流程是:

  1. 获取服务列表
  2. 路由
  3. 进行负载均衡
  4. 错误处理

对应三个不同的接口:

++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方法并且调用它

image.png

调用子类的doList方法,然后使用Router过滤


AbstractDirectory有两个实现类:StaticDirectory和RegistryDirectory

  • StaticDirectory:静态列表,通过构造方法传入一个Invoker的集合后不再改变,doList仅仅返回这个集合
  • RegistryDirectory:通过注册中心获取的动态Invoker列表

RegistryDirectory

RegistryDirectory在继承了AbstractDirectory,同时实现了NotifyListener,表示自己可以注册到zookeeper等注册中心,并且可以通过notify方法获取动态跟新 构造方法:

image.png

将url的信息解析成map,将method提取出来

image.png

调用registry的subscribe,将自己作为listener注册,注册成功后registry会调用notify方法通知

image.png

将回掉的urls分类后进行不同的处理,最后调用refreshInvoker方法刷新本地缓存可调用列表

image.png

refreshInvoker大致分为三步:

  1. 将原来的urlInvokerMap保存起来,将新传入的url放入缓存,为的是将新的url和旧的做比较,删除那些已经失效的服务
  2. 将url转换为invoker的map,如果以前已经存在过map,则不会重新引用,具体逻辑:
    1. 首先判断协议是否匹配 image.png 用获取的url匹配原始订阅URL中支持的协议,并且还要用SPI的方法去检查是否有该协议的扩展,如果不支持就忽略该url

    2. 获取invoker image.png 从原来的map获取inovker,如果没有就使用Protocol的refer方法,去获取一个invoker实例

然后将获取到的string-invoker的map转换为method-List的映射,具体就是循环解析url,将url方法信息解析出来并且聚合

  1. 对比以前的map和新的map,将没有使用到的invoker给销毁,调用invoker的destroy方法

到此为止,RegistryDirectory内部已经保存了第一次从注册中心获取到的信息,并且转换成了方法-invoker的映射,doList方法不过也就此基础上进行筛选

image.png

根据需要远程调用服务的信息,获取List的方法

还记得之前RegistryProtocol的DoRefer吗?

image.png

这里new了一个RegistryDirectory,并且调用了subscribe方法,在subscribe方法调用完成后就会触发notify,RegistryDirectory内部就已经有了方法的map。

随后就是调用黄线的cluster.join方法,Directory的list方法和laodBalance的select都是在Cluster内部完成,目前已经成功获取到了可用的inovker列表以及负载均衡,接下来就是容错的策略

LoadBalance

dubbo负载均衡

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

image.png

select首先对sticky属性进行了判断,sticky参数的含义是尽量让客户端向同一个服务提供者发起调用请求,如果包含了这个属性且服务是可用的,就不用进行负载均衡直接返回,不然就调用doSelect进行选择

image.png

selected参数表示这个list内的invoker是已经选择过了,这次调用是不可选的,当调用loadbalance选择出一个invoker后,需要判断是不是包含在这个selected列表中,如果配置了可用性检查还需要检查available属性;如果命中就需要重新选择,重新选择后也有可能选择出一个null的结果,那么就按照list的index向下顺延

reselect方法:

image.png

reselect方法有两个阶段:

  1. availablecheck表示检查isAvailable属性,首先遍历invokers列表,找出那些符合选择的invoker(不在selected中),添加到reselectInvokers的list,调用loadbalance方法选举
  2. 如果没有符合选择的条件,就从selected中选择isAvailable为true的再选择,还是没有就只能返回null了

可以发现在selected中的invoker也不一定就是被排除的,也有可能被选择到

以上是AbstractClusterInvoker中关于负载均衡的方法,如需使用尽在子类实现的doInvoke中调用

image.png

这是AbstractClusterInvoker的invoke方法,主要是调用list方法获取invoker列表,list方法底层由Directory.list实现

具体实现

Cluster一共有9种容错机制,其中有7中普通和2种特殊,见下表:

名称简介
AvailableCluster最简单的机制,遍历出一个可用的invoker,如果找不到抛出异常
BroadcastCluster广播所有可用的服务,任意一个服务报错就记录报错,最后抛出异常,由于是广播不需要负载均衡
FailbackCluster请求失败后会记录一个失败队列,由定时线程去重试,请求会负载均衡
FailfastCluster快速失败,请求会负载均衡,失败后快速抛出异常不重试
FailoverCluster重试机制,按照配置的重试次数重试,会负载均衡
FailsafeCluster出现异常直接忽略,不关心是否调用成功,会负载均衡
ForkingCluster同时调用多个服务,只要一个返回就返回,会进行多次负载均衡
MergeableCluster特殊机制,多个节点的请求结果合并
MockCluster特殊机制,请求失败的时候返回一个伪造或自定义的结果

  • AvailableCluster

image.png

遍历获取isAvailable为true的第一个,找不到就抛出异常

  • BroadcastCluster

image.png

遍历服务依此调用,任意一个服务报错后,在所有服务调用完毕后抛出异常,由于是广播不需要负载均衡,一般用于跟新服务状态

  • FailbackCluster

image.png

在失败后调用addFailed方法,将invoker加入hashmap

image.png

addFailed内部会初始化定时器,时间为5000ms一次执行retryFailed(),主要执行failed.put(invocation, router),加入map中

image.png

retryFailed遍历map,将invoker执行,如果还是失败就记录日志

failback适用于一些异步或最终一致性的服务,

  • FailfastCluster

image.png

进行了负载均衡,调用失败后直接返回错误,通常用于非幂等写操作

  • FailoverCluster

image.png

首先从url中解析出重试次数,在第二次重试及以后,都要重新获取invoker列表,因为可能会发生变动,使用invoked来记录已重试过的invoker,每次重试都需要负载均衡

Failover是默认的容错方式,一般用户读操作或幂等性写操作,由于重试会重新获取服务列表和负载均衡,会增大接口的延迟和服务的压力

  • FailsafeCluster

image.png

Failsafe直接忽略了异常,表示不关心返回,一般用于写日志或其他不重要的操作

  • ForkCluster

image.png

按照配置的最大并行数forks的值去进行多次负载均衡,开启了forks个线程去处理每个invoker,执行完毕后放入BlockingQueue队列,一旦某一个线程返回正常就结束,当线程抛出异常后用一个原子变量记录错误的次数,一旦全部错误,则抛出异常

Fork策略可以提高远程调用的性能,但是也会浪费更多的资源

  • MergeableCluster

Mergeable会按照自定义的策略,将多个invoker调用的结果给合并

image.png

开启多线程调用,结果使用Future接受,调用future.get会阻塞,这 里设置了超时时间默认1000ms,然后将结果放入resultList,后续按照规则解析

  • MockCluster

由于Cluter是一个SPI方法,会自动套上Wrapper类,而MockClusterWrapper是Cluter的Wrapper,表示在执行其他Cluter策略的时候都会先经过Mock的逻辑

image.png

看MockClusterInvoker内部

image.png

会检查url中是否有mock字段,配置force:xxx则强制走mock流程,配置fail:xxx则远程调用失败后使用mock,进入doMockInvoke方法

image.png

进入MockInovker看看

image.png

这个mock的参数可以配置为return XXX,throw XXX,或者是一个实现类,这里就是在解析mock字段的参数,然后配置按照规则返回,可以返回null或者是具体的值,也可有抛出错误和用一个默认的实现类去代替。

比如配置:force:true或者force:defalut,会到interface目录下找xxMock结尾的实现类;也可以详细配置:force:com.alibaba.dubbo.demo.DemoServiceMock


以上就是cluster接口以及实现方法,具体实现并不难