负载均衡策略对于RPC的请求吞吐来说影响重大,因此尝试了解了一下一些开源PRC框架中的负载均衡策略。

概况

简单看了三个项目的实现:dubbosofa-rpcbrpc-java。首先还是直接清单展示对比一下三个项目中的负载均衡策略,随后再来分析下每种策略的实现。

项目策略
dubboRandomLoadBalance
RoundRobinLoadBalance
ConsistentHashLoadBalance
LeastActiveLoadBalance
sofa-rpcRandomLoadBalancer
RoundRobinLoadBalancer
WeightRoundRobinLoadBalancer
ConsistentHashLoadBalancer
WeightConsistentHashLoadBalancer
LocalPreferenceLoadBalancer
brpc-javaRandomStrategy
RoundRobinStrategy
WeightStrategy
FairStrategy

项目

dubbo

RandomLoadBalance

随机策略基本上是每一个RPC框架都不会少的。RandomLoadBalance考虑了节点权重,在节点权重相同时从N个待选节点中随机均匀挑选一个目标节点,权重不同时则按权重进行随机挑选。RandomLoadBalance实现考虑了节点预热。预热期间节点会有一个预热权重,以此对请求流量进行调度,避免预热节点处理过多请求。

随机策略的优点在于实现简单,易于理解。

随机策略的缺点在于没有考虑其它信息因素,当某个节点出现异常变慢时,被选中的概率还是不变,这种情况下会导致整体吞吐大量下降。如果考虑权重因素,让外层逻辑去进行一些动态计算来调整不同节点的权重,那可以规避一些明显的问题点,不过那种情况下关键点就在于权重的计算调整,而非随机这一点。

RoundRobinLoadBalance

轮询策略也是一种经典策略。按方法分别轮询,不同方法之间互不影响。RoundRobinLoadBalance实现中考虑了权重,其计算逻辑中,每个节点维护自身计数,每次选择时,节点计数增加自身权重值。全部节点处理完成之后,选择计数最大的节点作为目标节点,同时其计数减去全局权重和。

轮询策略也容易理解实现。不考虑权重因素的话,轮询也没能考虑其它指标,也不能很好处理部分节点变慢等场景。权重的计算处理,则也同样是另外的问题。

ConsistentHashLoadBalance

一致性哈希策略。将相同请求的相同参数路由到同一个节点中。

直觉上这个策略不是特别通用,考虑极端场景,如果请求不够离散,可能部分节点都不一定能分配到请求。

不过这种策略也有适用的场景。相同参数请求路由到同一个节点,目标节点适合做一些本地化处理,如内存缓存之类。相对来说命中率能够更好,除此之外,没有其它特别合适的场景了。

LeastActiveLoadBalance

最小活跃连接策略。每一次分发请求时,选择当前活跃连接数最小的节点进行随机加权分配。

最小活跃连接相比其它策略多了一个信息维度,其隐含的假设是如果节点变慢活跃连接数就会多,活跃连接数少节点处理能力强。在选定最小活跃的基础上,再进行同等条件下的随机处理。

最小活跃策略的好处是在部分节点变慢的场景下,可以有更好的性能吞吐表现。慢节点不易分配更多的请求。

最小活跃策略的缺点在于其不兼容节点预热处理。新增节点的连接数少,请求会优先发给它们,在节点预热完成之前,会一波波的出现这种情况。

如果要让最小活跃策略兼容预热,大体有两种修改思路,

  • 加权最小活跃连接,将最小活跃连接转化为权值,这样就转化成了加权随机策略。这个思路的问题点在原权值转化如何处理。
  • 分组处理。将节点分为预热与正常节点两组,组之间按照权重选择,组内部按照最小活跃连接选择。

sofa-rpc

RandomLoadBalancer

sofa-rpc的随机策略与dubbo类似,都考虑了权重因素,实现相差无几。

RoundRobinLoadBalancer/WeightRoundRobinLoadBalancer

RoundRobinLoadBalancer是单纯的轮询实现,很简单的计数取模实现。

WeightRoundRobinLoadBalancer考虑了权重因素,没有考虑预热逻辑。注释里面不推荐使用。

ConsistentHashLoadBalancer/WeightConsistentHashLoadBalancer

与dubbo的ConsistentHashLoadBalance思路一致,WeightConsistentHashLoadBalancer额外考虑了节点权重,对于权重高的节点会分配更多的请求比例。本质上还是让相同请求路由到相同节点,便于节点内部做一些本地缓存类似的事情。

LocalPreferenceLoadBalancer

本机优先策略。优先选择客户端相同机器上的服务端节点,未命中则进行随机处理。

这个策略的目的看着是为了在节点混部情况下,更好的识别本机服务。本机相对其它的差别在于更快,如果能够识别出节点的处理能力,那么完全可以替代这种策略。

brpc-java

RandomStrategy

单纯的随机策略,没有考虑权重。

RoundRobinStrategy

单纯的轮询策略,没有考虑权重。

WeightStrategy

相当于带权重随机,权重值计算为错误次数少的节点权重值高。

FairStrategy

brpc-java前面几种策略实现都很简单,主要的策略放在了FairStrategy上。FairStrategy中的Fair体现在权重的计算。权重计算来源是每个连接维持的调用延迟时间。每一个节点维护一组延迟数据,权重计算根据平均延迟来确定。权重计算完成之后,FairStrategy内部采用了树形结构来维护节点权重关系,在选择节点时通过树形查找来加速。

FairStrategy策略上来看最终效果是让各节点的平均延迟均衡。实际整体上的效果表现是否比上面其它策略更优,要拿到同一套框架内去测试比较。逻辑上来看,它的权值计算是周期性的,相比较最小活跃连接策略,在节点出现问题时的响应及时性上应该不及。

总结

现实中可能基于最小活跃连接去改造会更为方便,拿到更好结果。不过上述三个框架中的策略基本都只是客户端维度上的处理,基于最简单的统计维度。实际项目中的情况可能更为复杂,例如服务端业务逻辑进行了限流快速熔断等处理,这种适合节点处理请求快延迟小并不代表节点更优。这种场景下,单纯的客户端维度统计可能并不足够,或许是要更细的去识别请求结果内容来进行判定。