likes
comments
collection
share

Dubbo消费端调用全过程实现分析(二)-负载均衡与集群容错

作者站长头像
站长
· 阅读数 19

前言

在前文<Dubbo消费端调用全过程实现分析(一)-代理类生成>中已经将消费端代理类的生成过程进行了较为详细的介绍,接下来会对消费端调用接口的执行过程的源码进行分析。下图即为上文中的总结:

Dubbo消费端调用全过程实现分析(二)-负载均衡与集群容错

本文将对调用过程中涉及到核心的负载均衡、集群容错的实现进行分析。

一、负载均衡

负载均衡的目的是保证服务提供者被调用的流量能够平均的分布,避免流量倾斜导致某些节点负载过高而影响可用性。简单的说,在存在多个服务提供者的情况下,消费端在每次发起调用时要通过合适的策略选择一个节点进行调用,而这种合适的选择策略就是所说的负载均衡策略。要注意的是,我们在说负载均衡时,有一个前提就是要存在1个以上的服务提供者。

Dubbo提供了五种常见的负载均衡策略,分别是随机、轮询、最少活跃调用、一致性Hash、最快响应,同时,使用者也可以自行扩展实现负载均衡策略。下面对Dubbo提供的四种负载均衡实现方案进行说明。

2.1 随机-Random LoadBalance

Dubbo默认的负载均衡机制是随机,随机负载均衡的实现可以在RandomLoadBalance类中查看。

假定存在n个可用的节点,则实现的步骤:

  • 为每一个节点生成权重weight,分别记为w1,w2....wn
  • 如果w1=w2=...=wn,或sum(w1+w2+...+wn)=0,则通过ThreadLocalRandom.current().nextInt(n)随机获取节点,每个节点被选中的概率是相同的
  • 反之(所有节点的权重不是全部一样并且总权重累计值大于0),所有权重之和记为totalWeight,则通过ThreadLocalRandom.current().nextInt(totalWeight)生成一个随机数offset,然后循环遍历节点依次使用offset减去各节点的权重值,直至offset被减至小于0时,就选取该节点

下面举例说明,假设存在3个提供者节点,最终计算出的权重分别为w1=100,w2=150,w3=200,如下图:

Dubbo消费端调用全过程实现分析(二)-负载均衡与集群容错

最终,会在范围内[0,450)生成一个随机数,随机数落在[0,100)范围内则取节点1,落在[100,250)则取节点2,落在[250,450)则取节点3。

那么,每个节点的权重值怎么来的?

首先是配置的权重weight,权重值的配置是在服务提供方,可通过API配置ServiceConfig.setWeight(xxx)、xml配置<dubbo:Service weight="xxx">等方式进行设置。如果没有主动配置,默认的权值就是100。

其次是预热时间warmup,默认的为10分钟,或者通过配置服务提供方的warmup(单位毫秒)。

最后是,启动的时间uptime,通过当前时间减去提供者启动的时间(provider的url里的参数timestamp)。

最终权重的计算公式为:

Dubbo消费端调用全过程实现分析(二)-负载均衡与集群容错 如果最终计算的finalWeight小于1时为兜底取1。

基于Dubbo随机负载均衡策略的实现,我们可以通过合理的权重配置来达到流量分配的效果,同时,基于服务启动的预热时间,避免让服务在启动之初就处于高负载状态,来提升调用的稳定性。

2.2 轮询-RoundRobin LoadBalance

轮询策略,即依次请求各节点,Dubbo实现的方式可在RoundRobinLoadBalance类中查看。

实现的关键是RoundRobinLoadBalance的内部类WeightedRoundRobin,该类存在三个成员变量:

  • weight:节点权值
  • current:节点当前的权重,用于选择执行节点,选择最大的current的节点用于执行
  • lastUpate:最近更新时间,当节点数量变化时,用于判断是否从节点map中移出

选择过程如下:

  • 轮询所有节点,计算所有节点的current = current + weight,过程中会累积计算节点总权重totalWeight
  • 选取节点中cur最大的节点用于调用候选节点
  • 更新选中节点的current值,即current=current-totalWeight

轮询策略存在一个缺点是当其中某个节点响应时间较长时,请求还是会轮流的到此节点继续请求。要注意的是,轮询策略对各节点的权重计算与随机策略的权重计算方式是一样的。所以,本质上来说轮询与随机两种负载均衡的策略可以归为同一类。

针对这类策略存在的问题,就有最少活跃调用策略。

2.3 最少活跃调用数-LeastActive LoadBalance

最少活跃调用数,从字面意思就很好理解,即选择节点时会优先选择正在被调用的数量最少的节点。

第一个关键点是怎么去获取每个提供端节点的正在被调用的数量?

在LeastActiveLoadBalance类中可以看到获取active的代码为:

int active = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName()).getActive();

由上可知活跃连接数量active是在RpcStatus中,通过查找发现在ActiveLimitFilter中会在调用前将RpcStatus中将连接数量加1,请求结束后会减1。具体可以查看ActiveLimitFilter代码,此处不详细展开介绍。

因此,我们在使用LeastActive LoadBalance这种负载均衡策略时,需要在消费端配置filter="activelimit"。如果没有配置,实际上还是根据权重随机选择策略。

Dubbo实现最少活跃调用数的说明:

  • 如果存在最少活跃调用数的节点,则选择此节点
  • 如果存在多个相同的最少活跃调用数的节点,则通过权重加随机值计算选择其中一个

2.4 一致性Hash-ConsistentHash LoadBalance

我们对Hash并不陌生,比如常见的HashMap,分库分表中使用Hash对数据进行分片等。简单来说,通过对给定的参数进行hash函数求值得到hash值,然后对节点进行取模选择。其特点是对于相同的参数,最后都能找到同一个目标节点,而且效率较高。但使用普通的Hash方式做负载均衡策略存在一个缺点,当集群进行扩容或缩容时,集群节点数发生变化,请求分布可能会发生剧烈抖动,也就是大部分请求都会改变请求的目的节点。如下所示:

Dubbo消费端调用全过程实现分析(二)-负载均衡与集群容错 现有4个服务提供节点,四个请求假设通过hash函数得出的值分别是100,101,102,103,然后与4取模就分别映射到node0,node1,node2,node3,此时如果节点node3挂掉了,则会出现如下情况

Dubbo消费端调用全过程实现分析(二)-负载均衡与集群容错 可见在hash值不变的情况下,每一个请求的请求节点都发生变化,这样就不符合hash要达到的预期效果。

那么,什么是一致性Hash?一致性Hash是指将各节点和数据都映射到一个首尾相连的哈希环上。将存储节点和数据都映射到一个首尾相连的哈希环上,计算出的Hash值依照顺时针方向找到相应的节点。为了避免节点变化时导致发生数据倾斜,通常会增加虚拟节点,Dubbo在实现时默认设置160个虚拟节点,也可通过hash.nodes进行设置。 关于一致性hash的介绍已有很多,此处不展开。

2.5 最短响应时间-ShortestResponseLoadBalance

最短响应时间策略可以说是在最少活跃连接的基础上衍生出来的,这里的响应时间指的是某一个提供方节点处理完当前活跃的请求所需要的时间。这两种策略的实现方式也基本一致,区别就在于最短响应策略需要关注节点响应的值是怎么计算的。以下为ShortestResponseLoadBalance类中关于计算节点响应时间的代码:

// ShortestResponseLoadBalance.java
​
 RpcStatus rpcStatus = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName());
 // Calculate the estimated response time from the product of active connections and succeeded average elapsed time.
 long succeededAverageElapsed = rpcStatus.getSucceededAverageElapsed();
 int active = rpcStatus.getActive();
 long estimateResponse = succeededAverageElapsed * active;

基本的数据来源还是最少活跃连接中提到的RpcStatus,通过历史的数据计算平均每次成功请求的耗时,然后再与活跃连接数active相乘得出的值就是节点的预估响应时间,然后在所有节点的预估响应时间中选择一个最小值。如果存在相同的就根据权重加随机选择,与最少活跃连接处理的方式一致。

2.6 粘滞连接

在负载均衡之前会去判断是否已配置了粘滞连接,如果配置了就会选择已调用过的invoke进行调用。

粘滞连接用于有状态服务,尽可能让客户端总是向同一提供者发起调用,除非该提供者挂了,再连另一台。

粘滞连接将自动开启延迟连接,以减少长连接数。

可通过如下配置:

<dubbo:reference id="xxxService" interface="com.xxx.XxxService" sticky="true" />

以上就是关于Dubbo负载均衡的相关学习内容,在集群环境下,有了负载均衡选择调用节点后,并不能保证每一次调用都能成功,由于网络总是存在不确认性、服务也存在故障的可能,那么就需要用对调用异常的情况进行容错处理的机制。

下面来介绍Dubbo的集群容错机制。

二、集群容错

Dubbo实现的集群容错有6种,从代码实现及思想来看都不算复杂,此处就不详细展开。总结如下:

容错模式特点使用场景
Failover调用失败后,默认重试2次通常用于读操作,但重试会带来更长延迟
Failfast调用1次,异常时会往上抛出异常通常用于非幂等的写入操作
Failsafe调用1次,当调用失败时,打印错误日志,并返回一个空的结果一般用于写入审计日志等
Failback调用失败时,会记录失败请求并使用定时器去重试,默认间隔5s,重试3次通常用于消息通知操作
Forking并行调用多个服务器,只要一个成功即返回通常用于实时性要求较高的读操作,但需要浪费更多服务资源
Broadcast广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息