前言

前面一篇文章提到了服务的消费,到最后还有一段关于集群的代码,在实际的生产环境中,zookeeper会有集群来提高服务的容错能力,所以dubbo编写了集群的代码去解决这个问题,集群一定会涉及到负载均衡,负载均衡的耦合性比较低适合单独拿出来分析,所以这一期介绍一下dubbo几种负载均衡策略以及实现

正文

dubbo负载均衡的接口是LoadBalance,使用了SPI注解

Dubbo源码分析(5)—— 负载均衡_Dubbo

接口只有一个select方法,传入一个可供选择的invokers,返回选择的invoker

AbstractLoadBalance继承了LoadBalance接口,并且定义了一个抽象方法doSelect,用于子类实现的模板方法。

Dubbo源码分析(5)—— 负载均衡_Dubbo_02

只有一个invoker直接返回

AbstractLoadBalance提供了一个计算服务提供者权重的方法,注释都写在内部了Dubbo源码分析(5)—— 负载均衡_Dubbo_03

Dubbo源码分析(5)—— 负载均衡_Dubbo_04


继承了AbstractLoadBalance一共有四个类,分别代表了不同的负载均衡策略:

  • ConsistentHashLoadBalance:一致性hash
  • RoundRobinLoadBalance:轮询
  • RandomLoadBalance:随机
  • LeastActiveLoadBalance:最少活跃数

下面分别介绍每种策略的实现细节

RandomLoadBalance 随机负载均衡

随机选择是最简单的,直接看代码,注释写得很明白 标题Dubbo源码分析(5)—— 负载均衡_负载均衡_05

RoundRobinLoadBalance 轮询负载均衡

轮询负载均衡的难点是带权重的轮询

比如一个有三个服务:[A,B,C],权重:[3,2,1],当进行6次负载均衡以后,A、B、C三台服务器选择的次数分别为3,2,1

dubbo采用的方法是:

  1. 初始化服务,维护一个current参数,初始化为0
  2. 遍历每个服务,各自加上自己的权重,权重最大的被选中。
  3. 被选中的服务减去总权重
  4. 重复上面的步骤

Dubbo源码分析(5)—— 负载均衡_Dubbo_06

轮询过程,6次轮询,A服务3次,B服务2次,C服务1次

代码

Dubbo源码分析(5)—— 负载均衡_Dubbo_07

用WeightedRoundRobin类来维护服务信息,用methodWeightMap来保存方法->多个服务map

Dubbo源码分析(5)—— 负载均衡_负载均衡_08

前面部分是创建map和获取key的操作,红框圈起来是核心业务代码,自增、计算weight的total和找到最大权重的服务。

Dubbo源码分析(5)—— 负载均衡_负载均衡_09

这一段代码是新加入了服务,或者是太长没有被选择服务,调整methodWeightMap

Dubbo源码分析(5)—— 负载均衡_Dubbo_10

最后就是选中的服务减去总权重,然后返回服务

LeastActiveLoadBalance 最小活跃负载均衡

Dubbo源码分析(5)—— 负载均衡_Dubbo_11

首先初始化信息

Dubbo源码分析(5)—— 负载均衡_负载均衡_12

然后遍历每个服务,遇到了最小的活跃数就重新计算,如果有相同的活跃度,则同样加入数组中。

Dubbo源码分析(5)—— 负载均衡_负载均衡_13

经过上面的操作后,会选出几个具有相同活跃度的服务,如果只有一个就直接返回,有多个的话根据权重按照之前介绍的随机方法进行选择。

ConsistentHashLoadBalance 一致性hash负载均衡

一致性Hash的原理是将服务化成一个环

Dubbo源码分析(5)—— 负载均衡_Dubbo_14

将数据Key使用相同的函数Hash计算出哈希值,并确定此数据在环上的位置,从此位置沿环顺时针查找,遇到的服务器就是其应该定位到的服务器

Dubbo源码分析(5)—— 负载均衡_Dubbo_15由于

对服务Ip加上编号,形成更多的虚拟IP,让一致性Hash的负载更加均衡

代码Dubbo源码分析(5)—— 负载均衡_Dubbo_16

使用一个Map,Value为ConsistentHashSelector来保存不同方法对应的集群,注意identityHashCode这个方法,它的返回值是原始的hashCode方法返回值一致,修改了hashCode也不会改变,这里有个Bug就是,如果传入invokers的时候对这个List做了stream操作,会导致list的地址改变,使这个map缓存无效。

进入ConsistentHashSelector类

Dubbo源码分析(5)—— 负载均衡_负载均衡_17

使用了TreeMap来作为节点hash的映射,因为TreeMap是树结构,能够更快的求出大于\小于的所有节点。

有关于一致性Hash的配置如下:

//需要哪些参数来生成hashCode,如果invoker(Obecjt var1,Object var2)
默认值为0,代表只使用var1来生成hashCode<dubbo:parameter key="hash.arguments" value="0,1" />//虚拟节点的数量,默认为160<dubbo:parameter key="hash.nodes" value="320" />复制代码

Dubbo源码分析(5)—— 负载均衡_Dubbo_18

根据Invoker和方法名称来初始化ConsistentHashSelector

红框部分:将所有的虚拟机节点4个分为一组,根据md5算法为每4个结点生成一个消息摘要,摘要长为16字节128位。然后通过hash算法计算出md5的hash值,放入TreeMap

Dubbo源码分析(5)—— 负载均衡_负载均衡_19

主要方法是selectForKey,使用TreeMap的tailMap方法,能够获取大于该hash值的所有服务,通过firstEntry获取大于的第一个服务,由于是一个环状结构,entry为空的话就获取TreeMap的第一个服务节点。

结尾

介绍了dubbo四种负载均衡策略,这四种也是比较常见的负载均衡算法,在Spring Cloud中也有体现,这里可以看出来实现还是比较容易。