2020 年 5 月 15 日,Dubbo 发布 2.7.7 release 版本。其中有这么一个 Features

format

新增一个负载均衡策略。

先看一下提交记录:

1
https://github.com/chickenlj/incubator-dubbo/commit/6d2ba7ec7b5a1cb7971143d4262d0a1bfc826d45

format1

负载均衡是基于 SPI 实现的,我们看到对应的文件中多了一个名为 shortestresponse 的 key。

这个,就是新增的负载均衡策略了。看名字,你也知道了这个策略的名称就叫:最短响应。

所以截止 2.7.7 版本,官方提供了五种负载均衡算法了,他们分别是:

  1. ConsistentHashLoadBalance 一致性哈希负载均衡
  2. LeastActiveLoadBalance 最小活跃数负载均衡
  3. RandomLoadBalance 加权随机负载均衡
  4. RoundRobinLoadBalance 加权轮询负载均衡
  5. ShortestResponseLoadBalance 最短响应时间负载均衡

最短响应时间负载均衡

首先,我们看一下这个类上的注解,先有个整体的认知。

1
org.apache.dubbo.rpc.cluster.loadbalance.ShortestResponseLoadBalance

format2

翻译一下是什么意思:

  1. 从多个服务提供者中选择出调用成功的且响应时间最短的服务提供者,由于满足这样条件的服务提供者有可能有多个。所以当选择出多个服务提供者后要根据他们的权重做分析。
  2. 但是如果只选择出来了一个,直接用选出来这个。
  3. 如果真的有多个,看它们的权重是否一样,如果不一样,则走加权随机算法的逻辑。
  4. 如果它们的权重是一样的,则随机调用一个。

再配个图,就好理解了,可以先不管图片中的标号:

format3

有了上面的整体概念的铺垫了,接下来分析源码的时候就简单了。

源码一共就 66 行,我把它分为 5 个片段去一一分析。

format4

这里一到五的标号,对应上面流程图中的标号。我们一个个的说。

标号为①的部分

format5

这一部分是定义并初始化一些参数,为接下来的代码服务的,翻译一下每个参数对应的注释:

length 参数:服务提供者的数量。

shortestResponse 参数:所有服务提供者的估计最短响应时间。(这个地方我觉得注释描述的不太准确,看后面的代码可以知道这只是一个零时变量,在循环中存储当前最短响应时间是多少。)

shortCount 参数:具有相同最短响应时间的服务提供者个数,初始化为 0。

shortestIndexes 参数:数组里面放的是具有相同最短响应时间的服务提供者的下标。

weights 参数:每一个服务提供者的权重。

totalWeight 参数:多个具有相同最短响应时间的服务提供者对应的预热(预热这个点还是挺重要的,在下面讲最小活跃数负载均衡的时候有详细说明)权重之和。

firstWeight 参数:第一个具有最短响应时间的服务提供者的权重。

sameWeight 参数:多个满足条件的提供者的权重是否一致。

标号为②的部分

format6

这一部分代码的关键,就在上面框起来的部分。而框起来的部分,最关键的地方,就在于第一行。

format7

获取调用成功的平均时间。

成功调用的平均时间怎么算的?

调用成功的请求数总数对应的总耗时 / 调用成功的请求数总数 = 成功调用的平均时间。

所以,在下面这个方法中,首先获取到了调用成功的请求数总数:

format8

这个 succeeded 参数是怎么来的呢?

format9

答案就是:总的请求数减去请求失败的数量,就是请求成功的总数!

那么为什么不能直接获取请求成功的总数呢?

因为在 RpcStatus 里面没有这个参数。

format10

请求成功的总数我们有了,接下来成功总耗时怎么拿到的呢?

format11

答案就是:总的请求时间减去请求失败的总时间,就是请求成功的总耗时!

那么为什么不能直接获取请求成功的总耗时呢?

别问,问就是……

我们看一下 RpcStatus 中的这几个参数是在哪里维护的:

1
org.apache.dubbo.rpc.RpcStatus#endCount(org.apache.dubbo.rpc.RpcStatus, long, boolean)

format12其中的第二个入参是本次请求调用时长,第三个入参是本次调用是否成功。

具体的方法不必细说了吧,已经显而易见了。

再回去看框起来的那三行代码:

format13

  1. 第一行获取到了该服务提供者成功请求的平均耗时。
  2. 第二行获取的是该服务提供者的活跃数,也就是堆积的请求数。
  3. 第三行获取的就是如果当前这个请求发给这个服务提供者预计需要等待的时间。乘以 active 的原因是因为它需要排在堆积的请求的后面嘛。

这里,我们就获取到了如果选择当前循环中的服务提供者的预计等待时间是多长。

后面的代码怎么写?

当然是出来一个更短的就把这个踢出去呀,或者出来一个一样长时间的就记录一下,接着去 pk 权重了。

所以,接下来 shortestIndexes 参数和 weights 参数就排上用场了:

format14

另外,多说一句的,它里面有这样的一行注释:

format15

和 LeastActiveLoadBalance 负载均衡策略一致,我给你截图对比一下:

format16可以看到,确实是非常的相似,只是一个是判断谁的响应时间短,一个是判断谁的活跃数低。

标号为③的地方

标号为③的地方是这样的:

format17

里面参数的含义我们都知道了,所以,标号为③的地方的含义就很好解释了:经过选择后只有一个服务提供者满足条件。所以,直接使用这个服务提供者。

标号为④的地方

format18

这个地方我就不展开讲了(后面的加权随机负载均衡那一小节有详细说明),熟悉的朋友一眼就看出来这是加权随机负载均衡的写法了。

不信?我给你对比一下:

format19

你看,是不是一模一样的。

标号为⑤的地方

format20

一行代码,没啥说的。就是从多个满足条件的且权重一样的服务提供者中随机选择一个。

如果一定要多说一句的话,我截个图吧:

format21

可以看到,这行代码在最短响应时间、加权随机、最小活跃数负载均衡策略中都出现了,且都在最后一行。