Dubbo——服务治理

服务治理


Dubbo提供了集群部署、路由、负载均衡等容错机制,在客户端引用服务时,由MockClusterInvoker封装具体的集群策略类,默认是FailoverCluster类,具体逻辑见《4.4.2 远程引用服务》部分。

Dubbo——服务治理_第1张图片

由集群策略类将Directory中的多个Invoker伪装成一个Invoker,对上层调用端是透明的,不同的集群策略有不同的处理方式。主要流程基本是一致的。

1)首先调用MockClusterInvoker.invoke方法,判断是调用Mock功能还是具体的集群策略类,具体逻辑见《消费端调用》部分。后续调用具体集群策略类的invoke方法。

2)调用Directory.list获取Invoker列表;

 3)调用Router.route进行路由选择;

4)LoadBalance进行负载均衡选择;

5)最后获取Invoker对象;

1 集群

一、服务的集群

Dubbo——服务治理_第2张图片

上图是集群服务的Invoker类的封装顺序。其中,FailoverClusterInovker是默认的集群策略类。

 

二、注册中心的集群

       对于注册中心的Invoker代理的创建,在ReferenceConfig.createProxy方法中处理。

       若在消费端配置一个注册地址的,则直接由代理工厂Protocol$Adpative根据注册地址URL中的协议选择RegistryProtocol类,然后调用该类的refer ()方法创建Invoker代理对象并封装成MockClusterInvoker对象返回;

       若在消费端配置多个注册地址的,则执行以下逻辑:

1、以每个注册地址URL为参数调用RegistryProtocol. refer ()方法创建Invoker代理对象并封装成MockClusterInvoker对象,生成了Invoker代理列表;

2、选择注册地址列表中最后一个注册地址作为创建最外层MockClusterInvoker对象的URL地址,大致逻辑是:

 2.1)在该URL地址中增加cluster参数以及值“available”,表示在存在多个注册地址的情况下只能使用AvailableCluster集群策略;

 2.2)使用StaticDirectory对第1步中生成的Invoker代理列表进行封装,将该列表存入StaticDirectory对象的invokers:List变量中。

 2.3)调用AvailableCluster类的join(Directory directory)方法将StaticDirectory对象封装成FailoverClusterInvoker对象并返回。由于Cluster类名文件中MockClusterWrapper类存在以该Cluster对象为参数的构造函数,故调用MockClusterWrapper.join方法将FailoverClusterInvoker对象再次封装,封装成MockClusterInvoker对象并返回。MockClusterInvoker主要是处理服务mock的情况。 

        对于多注册地址来说,在进行服务调用时,就要做两个集群选择,第一次是使用最外层的 AvailableCluster 策略类从注册中心集群中选择一个注册 Invoker MockClusterInvoker 对象,该对象表示一个注册中心地址),然后由该 MockClusterInvoker 对象从所代表的注册中心地址中选择选择具体服务的 Invoker ,即在集群策略 Invoker 中进行路由和负载均衡策略选择之后确定使用哪个具体的服务 Invoker
Dubbo——服务治理_第3张图片

三、集群策略的种类

1)AvailableCluster: 获取可用的调用。遍历所有Invokers判断Invoker.isAvalible,只要一个有为true直接调用返回,不管成功与否;

2)BroadcastCluster: 广播调用。遍历所有Invokers, 逐个调用每个调用catch住异常不影响其他invoker调用;

3)FailbackCluster: 失败自动恢复, 对于invoker调用失败, 后台记录失败请求,任务定时重发, 通常用于通知;

4)FailfastCluster: 快速失败,只发起一次调用,失败立即保错,通常用于非幂等性操作;

5)FailoverCluster: 失败转移,当出现失败,重试其它服务器,通常用于读操作,但重试会带来更长延迟;

1)目录服务directory.list(invocation) 列出方法的所有可调用服务列表(Invoker列表),

2)从URL中获取参数loadbalance的值,根据该参数值选择负载均衡策略类,默认为RandomLoadBalance

3)获取重试次数,参数retries,默认重试两次;然后遍历retries次下面的逻辑,直到成功返回或者达到重试次数。

     (2.1)若不是第一次调用(即是重试),则检查服务是否被销毁以及服务引用Invoker列表是否还有记录,若被销毁或者无服务引用了则直接抛异常;

     (2.2)使用负载均衡策略从Inovker列表中选择本次使用的Inovker;并将该Invoker对象存入invokered数组变量中(即记录已经使用过的Invoker对象,便于重试时不再使用);

    (2.3)若成功则返回;若失败则继续重复执行2.12.3的逻辑;

4 )重试次数达到最大值则抛出调用失败异常。

6)FailsafeCluster: 失败安全,出现异常时,直接忽略,通常用于写入审计日志等操作。

7)ForkingCluster: 并行调用,只要一个成功即返回,通常用于实时性要求较高的操作,但需要浪费更多服务资源。

8)MergeableCluster: 分组聚合, 按组合并返回结果,比如菜单服务,接口一样,但有多种实现,用group区分,现在消费方需从每种group中调用一次返回结果,合并结果返回,这样就可以实现聚合菜单项。

(1) 根据MERGE_KEY从url获取参数值;

(2) 为空不需要merge, 正常调用;

(3) 按group分组调用,将返回接口保存到集合中;

(4) 获取MERGE_KEY如果是默认的话,获取默认merge策略,主要根据返回类型判断;

(5) 如果不是,获取自定义的merge策略;

(6) Merge策略合并调用结果返回;

2 目录服务Directory

      在每个集群策略Cluster类中,都是通过Directory.list来查找所有的Invoker列表,注册中心的集群策略使用的是StaticDirectory,服务的集群策略使用的是RegistryDirectory;这两个类均是AbstractDirectory的子类,在获取Invoker列表均是调用该父类的list方法,然后在该方法内部调用子类的dolist方法。

         StaticDirectory:静态目录服务,该类的所有Invoker是通过构造函数传入的;对于多注册地址,首先遍历所有注册地址,以每个注册地址URL为参数调用RegistryProtocol. refer ()方法创建Invoker代理对象并封装成MockClusterInvoker对象,生成了Invoker代理列表;然后使用最后一个注册地址的URL以及Invoker列表来初始化StaticDirectory对象;如下代码所示。在StaticDirectory . dolist方法中直接返回所有多注册地址生成的invoker列表

Dubbo——服务治理_第4张图片
RegistryDirectory :注册目录服务,它实现了 NotifyListener 接口中的回调接口 notify 方法,它的 Invoker 列表是从注册中心获取的,具体逻辑见《 4.2 远程引用服务》部分。 RegistryDirectory. dolist 方法根据服务调用方法从 methodInvokerMap 变量中获取所有的远程服务引用的 invoker 执行对象。

3 路由选择

在AbstractDirectory.list方法中,通过具体的Directory.dolist方法获取到Invoker列表之后,然后根据Router列表对Invoker进行路由选择,该Router列表是在远程引用服务时初始化的,具体逻辑见《4.4.2远程引用服务》部分。

         路由规则分为条件路由规则(ConditionRouter:)和脚本路由规则(ScriptRouter:)两种:

ConditionRouter: 条件表达式以 => 分割为whenRule和thenRule

ConditionRouter创建时:

1)从url根据RULE_KEY获取路由条件路由内容

2)rule.indexOf("=>") 分割路由内容

3)分别调用parseRule(rule) 解析路由为whenRule和thenRules

ConditionRouter执行route方法时:

1)如果url不满足when条件即过来条件, 不过滤返回所有invokers;

2)遍历所有invokers判断是否满足then条件, 将满足条件的加入集合result;

3)Result不为空,有满足条件的invokers返回;

4)Result为空, 没有满足条件的invokers, 判断参数FORCE_KEY是否强制过来,如果强制过滤返回空, 不是返回所有即不过滤;

 

ScriptRouter:通过url的RULE_KEY参数获取脚本内容,然后通过java的脚本引擎执行脚本代码, dubbo的测试用例都是通过javascript作为脚本但是理论上也支持groovy, jruby脚本,大家可以参考下测试用例ScriptRouterTest。

ScriptRouter创建时:

1) 从url获取脚本类型javascript, groovy等等;

2) 从url根据RULE_KEY获取路由规则内容;

3) 根据脚本类型获取java支持的脚本执行引擎;

ScriptRouter执行route方法时:

1) 执行引擎创建参数绑定;

2) 绑定执行的参数;

3) 执行引擎编译路由规则得到执行函数CompiledScript;

4) CompiledScript.eval(binds) 根据参数执行路由规则;

4 负载均衡

LoadBalance负载均衡, 负责从多个 Invokers中选出具体的一个Invoker用于本次调用,调用过程中包含了负载均衡的算法,调用失败后需要重新选择。目前有RandomLoadBalance、RoundRobinLoadBalance、LeastActiveLoadBalance、ConsistentHashLoadBalance四种负载均衡策略可用。

RandomLoadBalance:随机访问策略,按权重设置随机概率,是默认策略,具体算法如下:

1)获取所有invokers的个数;

2)遍历所有Invokers, 获取计算每个invokers的权重,并把权重累计加起来;每相邻的两个invoker比较他们的权重是否一样,有一个不一样说明权重不均等;

3) 总权重大于零且权重不均等的情况下;按总权重获取随机数offset = random.netx(totalWeight); 遍历invokers确定随机数offset落在哪个片段(invoker上) ;

4) 权重相同或者总权重为0, 根据invokers个数均等选择:invokers.get(random.nextInt(length));

实现代码如下:
Dubbo——服务治理_第5张图片

RoundRobinLoadBalance:轮询,按公约后的权重设置轮询比率

1) 获取轮询key = 服务名+方法名;

2)获取可供调用的invokers个数length;

3)设置最大权重的默认值maxWeight=0

4)设置最小权重的默认值minWeight=Integer.MAX_VALUE

5)遍历所有Inokers,比较出得出maxWeight和minWeight

6) 如果权重是不一样的,

6.1)根据key获取自增序列,自增序列加一与最大权重取模默认得到currentWeigth ;

6.2)遍历所有invokers筛选出大于currentWeight的invokers

6.3)设置可供调用的invokers的个数length;

6.4)若length=1,则直接返回该invoker;若大于1则进入第7步;

7) 自增序列加一并与length取模,从invokers获取invoker;

实现代码如下:

Dubbo——服务治理_第6张图片

LeastActiveLoadBalance:最少活跃调用数,相同的活跃的随机选择。

活跃数是指调用前后的计数差, 使慢的提供者收到更少的请求,因为越慢的提供者前后的计数差越大。

活跃计数的功能消费者是在ActiveLimitFilter中设置的,代码如下,利用RpcStatus类的静态变量来统计功能方法的活跃数、调用总次数、总耗时、成功数、失败数等信息。

Dubbo——服务治理_第7张图片

最少活跃的选择过程如下:

1)获取可调用invoker的总个数,初始化最小活跃数,相同最小活跃的个数,相同最小活跃数的下标数组等等;

2) 遍历所有invokers,获取每个invoker的活跃数active和权重;找出最小权重的invoker;

如果有相同最小权重的inovkers, 将下标记录到数组leastIndexs[]数组中,

累计所有的权重到totalWeight变量;

3)如果invokers的权重不相等且totalWeight大于0;

按总权重随机offsetWeight = random.nextInt(totalWeight),计算随机值在哪个片段上并返回invoker;

4)如果invokers的权重相等或者totalWeight等于0,均等随机;

实现代码如下:
Dubbo——服务治理_第8张图片

ConsistentHashLoadBalance:一致性hash, 相同参数的请求总是发到同一个提供者,当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。




你可能感兴趣的:(dubbo)