本文来详细说下dubbo的负载均衡,常见的概念,性质,原理等。

文章目录

  • 负载均衡概述
  • dubbo中的负载均衡
    • 对多注册中心负载均衡
    • 对多服务负载均衡
  • dubbo中的负载均衡算法详解
    • 整体结构
    • RandomLoadBalance(随机算法)
    • RoundRobinLoadBalance(轮询算法)
    • LeastActiveLoadBalance(最少活跃数算法)
    • ConsistentHashLoadBalance(一致性Hash算法)
    • ShortestResponseLoadBalance(最短响应时间算法)
  • 本文小结

负载均衡概述

负载均衡是指在集群中,将多个数据请求分散在不同单元上进行执行,主要为了提高系统容错能力和加强系统对数据的处理能力。

在 Dubbo 中,一次服务的调用就是对所有实体域 Invoker 的一次筛选过滤,最终选定具体调用的 Invoker。首先在 Directory 中获取全部 Invoker 列表,通过路由筛选出符合规则的 Invoker,最后再经过负载均衡选出具体的 Invoker。所以 Dubbo 负载均衡机制是决定一次服务调用使用哪个提供者的服务


dubbo中的负载均衡

dubbo运行的过程中,在两个位置会使用负载均衡。

  1. 有多个注册中心时,客户端使用负载均衡选择其中一个注册中心上注册的服务;
  2. 客户端使用负载均衡选择一个注册中心上注册的多个服务。

这两个位置使用相同的配置选择负载均衡算法,通过

@Reference(loadbalance = "XXXX")

指定,所以这两个位置使用的负载均衡算法是相同的。


对多注册中心负载均衡

当存在多个注册中心时,客户端启动的时候,会创建ZoneAwareClusterInvoker对象(该对象不能使用SPI替换)。ZoneAwareClusterInvoker对象包含客户端注册到注册中心的url和MockClusterInvoker对象集合,每个注册中心对应一个MockClusterInvoker对象。

当客户端调用远程服务时,客户端调用ZoneAwareClusterInvoker的doInvoke方法,doInvoke方法使用配置的负载均衡类选择MockClusterInvoker对象集合中的一个对象。

如果通过负载均衡找到的MockClusterInvoker对象是不可用的,dubbo会遍历MockClusterInvoker对象集合,选择集合中第一个可用的对象。


对多服务负载均衡

客户端使用MockClusterInvoker对象调用远程服务。默认情况下,MockClusterInvoker对象将调用远程服务的任务委托给FailoverClusterInvoker。FailoverClusterInvoker通过select方法选择合适的服务。select方法的入参包括负载均衡对象,InvokerWrapper对象集合等,其中每个服务对应一个InvokerWrapper对象,InvokerWrapper对象是对远程调用服务的包装类。


dubbo中的负载均衡算法详解

LoadBalance(负载均衡)的职责是将网络请求或者其他形式的负载“均摊”到不同的服务节点上,从而避免服务集群中部分节点压力过大、资源紧张,而另一部分节点比较空闲的情况。

通过合理的负载均衡算法,可以让每个服务节点获取到适合自己处理能力的负载,实现处理能力和流量的合理分配。常用的负载均衡可分为软件负载均衡(比如,日常工作中使用的 Nginx)和硬件负载均衡(主要有 F5、Array、NetScaler 等,不过开发工程师在实践中很少直接接触到)。

常见的 RPC 框架中都有负载均衡的概念和相应的实现,Dubbo 也不例外。Dubbo 需要对 Consumer 的调用请求进行分配,避免少数 Provider 节点负载过大,而剩余的其他 Provider 节点处于空闲的状态。因为当 Provider 负载过大时,就会导致一部分请求超时、丢失等一系列问题发生,造成线上故障。


整体结构

Dubbo 负载均衡的分析入口是 org.apache.dubbo.rpc.cluster.loadbalance.AbstractLoadBalance 抽象类,查看这个类继承关系。

这个被 RandomLoadBalance、LeastActiveLoadBalance、RoundRobinLoadBalance、 ShortestResponseLoadBalance及 ConsistentHashLoadBalance 类继承,这5个类是 Dubbo 中提供的五种负载均衡算法的实现

名称 说明
RandomLoadBalance 随机算法,根据权重设置随机的概率
LeastActiveLoadBalance 最少活跃数算法,指请求数和完成数之差,使执行效率高的服务接收更多请求
RoundRobinLoadBalance 加权轮询算法,根据权重设置轮询比例
ConsistentHashLoadBalance Hash 一致性算法,相同请求参数分配到相同提供者
ShortestResponseLoadBalance 基于最短响应时间的算法

LoadBalance 是一个扩展接口,默认使用的扩展实现是 RandomLoadBalance,其定义如下所示,其中的 @Adaptive 注解参数为 loadbalance,即动态生成的适配器会按照 URL 中的 loadbalance 参数值选择扩展实现类。

@SPI("random")
public interface LoadBalance {@Adaptive({"loadbalance"})<T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;
}

LoadBalance 接口中 select() 方法的核心功能是根据传入的 URL 和 Invocation,以及自身的负载均衡算法,从 Invoker 集合中选择一个 Invoker 返回。

AbstractLoadBalance 抽象类并没有真正实现 select() 方法,只是对 Invoker 集合为空或是只包含一个 Invoker 对象的特殊情况进行了处理,具体实现如下:

 public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) {if (CollectionUtils.isEmpty(invokers)) {// Invoker集合为空,直接返回nullreturn null;} else {// Invoker集合只包含一个Invoker,则直接返回该Invoker对象// Invoker集合包含多个Invoker对象时,交给doSelect()方法处理,这是个抽象方法,留给子类具体实现return invokers.size() == 1 ? (Invoker)invokers.get(0) : this.doSelect(invokers, url, invocation);}}

另外,AbstractLoadBalance 还提供了一个 getWeight() 方法,该方法用于计算 Provider 权重,具体实现如下:

 int getWeight(Invoker<?> invoker, Invocation invocation) {URL url = invoker.getUrl();int weight;if ("org.apache.dubbo.registry.RegistryService".equals(url.getServiceInterface())) {// 如果是RegistryService接口的话,直接获取权重即可weight = url.getParameter("registry.weight", 100);} else {weight = url.getMethodParameter(invocation.getMethodName(), "weight", 100);if (weight > 0) {// 获取服务提供者的启动时间戳long timestamp = invoker.getUrl().getParameter("timestamp", 0L);if (timestamp > 0L) {// 计算Provider运行时长long uptime = System.currentTimeMillis() - timestamp;if (uptime < 0L) {return 1;}// 计算Provider预热时长int warmup = invoker.getUrl().getParameter("warmup", 600000);// 如果Provider运行时间小于预热时间,则该Provider节点可能还在预热阶段,// 需要重新计算服务权重(降低其权重)if (uptime > 0L && uptime < (long)warmup) {weight = calculateWarmupWeight((int)uptime, warmup, weight);}}}}return Math.max(weight, 0);}

calculateWarmupWeight() 方法的目的是对还在预热状态的 Provider 节点进行降权,避免 Provider 一启动就有大量请求涌进来。服务预热是一个优化手段,这是由 JVM 本身的一些特性决定的,例如,JIT 等方面的优化,我们一般会在服务启动之后,让其在小流量状态下运行一段时间,然后再逐步放大流量。

static int calculateWarmupWeight(int uptime, int warmup, int weight) {// 计算权重,随着服务运行时间uptime增大,权重ww的值会慢慢接近配置值weightint ww = (int)((float)uptime / ((float)warmup / (float)weight));return ww < 1 ? 1 : Math.min(ww, weight);
}

了解了 LoadBalance 接口的定义以及 AbstractLoadBalance 提供的公共能力之后,下面我们开始逐个介绍 LoadBalance 接口的具体实现。


RandomLoadBalance(随机算法)

加权随机算法负载均衡策略(RandomLoadBalance)是 dubbo 负载均衡的默认实现方式,根据权重分配各个 Invoker 随机选中的比例。这里的意思是:将到达负载均衡流程的 Invoker 列表中的 权重进行求和,然后求出单个 Invoker 权重在总权重中的占比,随机数就在总权重值的范围内生成。

如图,假如当前有192.168.1.10和192.168.1.11两个负载均衡的服务,权重分别为 4、6 ,则它们的被选中的比例为 2/5、3/5。


当生成随机数为 6 时,就会选中192.168.1.11的服务

dubbo 中 RandomLoadBalance的 doSelect 实现代码:

public class RandomLoadBalance extends AbstractLoadBalance {public static final String NAME = "random";@Overrideprotected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {// Invoker 数量int length = invokers.size();// 标识所有 Invoker 的权重是否都一样boolean sameWeight = true;// 用一个数组保存每个 Invoker 的权重int[] weights = new int[length];// 第一个 Invoker 的权重int firstWeight = getWeight(invokers.get(0), invocation);weights[0] = firstWeight;// 求和总权重int totalWeight = firstWeight;for (int i = 1; i < length; i++) {int weight = getWeight(invokers.get(i), invocation);// 保存每个 Invoker 的权重到数组总weights[i] = weight;// 累加求和总权重totalWeight += weight;// 如果不是所有 Invoker 的权重都一样,就给标记上 sameWeight = falseif (sameWeight && weight != firstWeight) {sameWeight = false;}}// 计算随机数取到的 Invoker,条件是必须总权重大于0,并且每个 Invoker 的权重都不一样if (totalWeight > 0 && !sameWeight) {// 基于 0~总数 范围内生成随机数int offset = ThreadLocalRandom.current().nextInt(totalWeight);// 计算随机数对应的 Invokerfor (int i = 0; i < length; i++) {offset -= weights[i];if (offset < 0) {return invokers.get(i);}}}// 如果所有 Invoker 的权重都一样则随机从 Invoker 列表中返回一个return invokers.get(ThreadLocalRandom.current().nextInt(length));}}

以上就是加权随机策略的实现,这里比较主要关注计算生成的随机数对应的 Invoker。通过遍历权重数组,生成的数累减当前权重值,当 offset 为 0 时,就表示 offset 对应当前的 Invoker 服务。

以生成的随机数为 6 为例,遍历 Invokers 长度:

  • 第一轮:offset = 6 - 4 = 2 不满足 offset < 0,继续遍历。
  • 第二轮:offset = 2 - 6 = -4 满足 offset < 0,返回当前索引对应的 Invoker。因为 offset 返回负数,表示 offset 落在当前 Invoker 权重的区间里。

加权随机策略并非一定按照比例被选到,理论上调用次数越多,分布的比例越接近权重所占的比例。


RoundRobinLoadBalance(轮询算法)

加权轮询负载均衡策略(RoundRobinLoadBalance)是基于权重来决定轮询的比例。普通轮询会将请求均匀的分布在每个节点,但不能很好调节不同性能服务器的请求处理,所以加权负载均衡来根据权重在轮询机制中分配相对应的请求比例给每台服务器。

public class RoundRobinLoadBalance extends AbstractLoadBalance {public static final String NAME = "roundrobin";private static final int RECYCLE_PERIOD = 60000;protected static class WeightedRoundRobin {private int weight;private AtomicLong current = new AtomicLong(0);private long lastUpdate;public int getWeight() {return weight;}public void setWeight(int weight) {this.weight = weight;current.set(0);}public long increaseCurrent() {return current.addAndGet(weight);}public void sel(int total) {current.addAndGet(-1 * total);}public long getLastUpdate() {return lastUpdate;}public void setLastUpdate(long lastUpdate) {this.lastUpdate = lastUpdate;}}private ConcurrentMap<String, ConcurrentMap<String, WeightedRoundRobin>> methodWeightMap = new ConcurrentHashMap<String, ConcurrentMap<String, WeightedRoundRobin>>();private AtomicBoolean updateLock = new AtomicBoolean();/*** get invoker addr list cached for specified invocation* <p>* <b>for unit test only</b>* * @param invokers* @param invocation* @return*/protected <T> Collection<String> getInvokerAddrList(List<Invoker<T>> invokers, Invocation invocation) {String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();Map<String, WeightedRoundRobin> map = methodWeightMap.get(key);if (map != null) {return map.keySet();}return null;}@Overrideprotected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {// key 为 接口名+方法名String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();// 查看缓存中是否存在相应服务接口的信息,如果没有则新添加一个元素到缓存中ConcurrentMap<String, WeightedRoundRobin> map = methodWeightMap.get(key);if (map == null) {methodWeightMap.putIfAbsent(key, new ConcurrentHashMap<String, WeightedRoundRobin>());map = methodWeightMap.get(key);}// 总权重int totalWeight = 0;long maxCurrent = Long.MIN_VALUE;// 当前时间戳long now = System.currentTimeMillis();// 最大 current 的 InvokerInvoker<T> selectedInvoker = null;// 保存选中的 WeightedRoundRobin 对象WeightedRoundRobin selectedWRR = null;// 遍历 Invokers 列表for (Invoker<T> invoker : invokers) {// 从缓存中获取 WeightedRoundRobin 对象String identifyString = invoker.getUrl().toIdentityString();WeightedRoundRobin weightedRoundRobin = map.get(identifyString);// 获取当前 Invoker 对象int weight = getWeight(invoker, invocation);// 如果当前 Invoker 没有对应的 WeightedRoundRobin 对象,则新增一个if (weightedRoundRobin == null) {weightedRoundRobin = new WeightedRoundRobin();weightedRoundRobin.setWeight(weight);map.putIfAbsent(identifyString, weightedRoundRobin);}// 如果当前 Invoker 权重不等于对应的 WeightedRoundRobin 对象中的权重,则重新设置当前权重到对应的 WeightedRoundRobin 对象中if (weight != weightedRoundRobin.getWeight()) {weightedRoundRobin.setWeight(weight);}// 累加权重到 current 中long cur = weightedRoundRobin.increaseCurrent();// 设置 weightedRoundRobin 对象最后更新时间weightedRoundRobin.setLastUpdate(now);// 最大 current 的 Invoker,并赋值给相应的变量if (cur > maxCurrent) {maxCurrent = cur;selectedInvoker = invoker;selectedWRR = weightedRoundRobin;}// 累加权重到总权重中totalWeight += weight;}// 如果 Invokers 列表中的数量不等于缓存map中的数量if (!updateLock.get() && invokers.size() != map.size()) {if (updateLock.compareAndSet(false, true)) {try {// 拷贝 map 到 newMap 中ConcurrentMap<String, WeightedRoundRobin> newMap = new ConcurrentHashMap<String, WeightedRoundRobin>();newMap.putAll(map);// newMap 转化为 IteratorIterator<Entry<String, WeightedRoundRobin>> it = newMap.entrySet().iterator();// 循环删除超过设定时长没更新的缓存while (it.hasNext()) {Entry<String, WeightedRoundRobin> item = it.next();if (now - item.getValue().getLastUpdate() > RECYCLE_PERIOD) {it.remove();}}// 将当前newMap服务缓存中methodWeightMap.put(key, newMap);} finally {updateLock.set(false);}}}// 如果存在被选中的 Invokerif (selectedInvoker != null) {// 计算 current = current - totalWeightselectedWRR.sel(totalWeight);return selectedInvoker;}// 正常情况这里不会到达return invokers.get(0);}}

上面选中 Invoker 逻辑为:每个 Invoker 都有一个 current 值,初始值为自身权重。在每个 Invoker 中current = current + weight。遍历完 Invoker 后,current 最大的那个 Invoker 就是本次选中的 Invoker。选中 Invoker 后,将本次 current 值计算current = current - totalWeight。

以上面192.168.1.10和192.168.1.11两个负载均衡的服务,权重分别为 4、6 。基于选中前current = current + weight、选中后current = current - totalWeight计算公式得出如下

请求次数 选中前current 选中后 current 被选中服务
1 [4, 6] [4, -4] 192.168.1.11
2 [8, 2] [-2, 2] 192.168.1.10
3 [2, 8] [2, -2] 192.168.1.11
4 [6, 4] [-4, 4] 192.168.1.10
5 [0, 10] [0, 0] 192.168.1.11

LeastActiveLoadBalance(最少活跃数算法)

最小活跃数负载均衡策略(LeastActiveLoadBalance)是从最小活跃数的 Invoker 中进行选择。什么是活跃数呢?活跃数是一个 Invoker 正在处理的请求的数量,当 Invoker 开始处理请求时,会将活跃数加 1,完成请求处理后,将相应 Invoker 的活跃数减 1。找出最小活跃数后,最后根据权重进行选择最终的 Invoker。如果最后找出的最小活跃数相同,则随机从中选中一个 Invoker。

public class LeastActiveLoadBalance extends AbstractLoadBalance {public static final String NAME = "leastactive";@Overrideprotected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {// Invoker 数量int length = invokers.size();// 所有 Invoker 中的最小活跃值都是 -1int leastActive = -1;// 最小活跃值 Invoker 的数量int leastCount = 0;// 最小活跃值 Invoker 在 Invokers 列表中对应的下标位置int[] leastIndexes = new int[length];// 保存每个 Invoker 的权重int[] weights = new int[length];// 总权重int totalWeight = 0;// 第一个最小活跃数的权重int firstWeight = 0;// 最小活跃数 Invoker 列表的权重是否一样boolean sameWeight = true;// 找出最小活跃数 Invoker 的下标for (int i = 0; i < length; i++) {Invoker<T> invoker = invokers.get(i);// 获取最小活跃数int active = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName()).getActive();// 获取权重int afterWarmup = getWeight(invoker, invocation);// 保存权重weights[i] = afterWarmup;// 如果当前最小活跃数为-1(-1为最小值)或小于leastActiveif (leastActive == -1 || active < leastActive) {// 重置最小活跃数leastActive = active;// 重置最小活跃数 Invoker 的数量leastCount = 1;// 保存当前 Invoker 在 Invokers 列表中的索引至leastIndexes数组中leastIndexes[0] = i;// 重置最小活跃数 invoker 的总权重值totalWeight = afterWarmup;// 记录当前 Invoker 权重为第一个最小活跃数 Invoker 的权重firstWeight = afterWarmup;// 因为当前 Invoker 重置为第一个最小活跃数 Invoker ,所以标识所有最小活跃数 Invoker 权重都一样的值为 truesameWeight = true;// 如果当前最小活跃数和已声明的最小活跃数相等 } else if (active == leastActive) {// 记录当前 Invoker 的位置leastIndexes[leastCount++] = i;// 累加当前 Invoker 权重到总权重中totalWeight += afterWarmup;// 如果当前权重与firstWeight不相等,则将 sameWeight 改为 falseif (sameWeight && i > 0&& afterWarmup != firstWeight) {sameWeight = false;}}}// 如果最小活跃数 Invoker 只有一个,直接返回该 Invokerif (leastCount == 1) {return invokers.get(leastIndexes[0]);}if (!sameWeight && totalWeight > 0) {// 根据权重随机从最小活跃数 Invoker 列表中选择一个 int offsetWeight = ThreadLocalRandom.current().nextInt(totalWeight);for (int i = 0; i < leastCount; i++) {int leastIndex = leastIndexes[i];offsetWeight -= weights[leastIndex];if (offsetWeight < 0) {return invokers.get(leastIndex);}}}// 如果所有 Invoker 的权重都一样则随机从 Invoker 列表中返回一个return invokers.get(leastIndexes[ThreadLocalRandom.current().nextInt(leastCount)]);}
}

这段代码的整个逻辑就是,从 Invokers 列表中筛选出最小活跃数的 Invoker,然后类似加权随机算法策略方式选择最终的 Invoker 服务。


ConsistentHashLoadBalance(一致性Hash算法)

一致性 Hash 负载均衡策略(ConsistentHashLoadBalance)是让参数相同的请求分配到同一机器上。把每个服务节点分布在一个环上,请求也分布在环形中。以请求在环上的位置,顺时针寻找环上第一个服务节点。如图所示:


同时,为避免请求散列不均匀,dubbo 中会将每个 Invoker 再虚拟多个节点出来,使得请求调用更加均匀。

一致性 Hash 实现如下:

public class ConsistentHashLoadBalance extends AbstractLoadBalance {public static final String NAME = "consistenthash";/*** Hash nodes name*/public static final String HASH_NODES = "hash.nodes";/*** Hash arguments name*/public static final String HASH_ARGUMENTS = "hash.arguments";private final ConcurrentMap<String, ConsistentHashSelector<?>> selectors = new ConcurrentHashMap<String, ConsistentHashSelector<?>>();@SuppressWarnings("unchecked")@Overrideprotected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {// 获取请求的方法名String methodName = RpcUtils.getMethodName(invocation);// key = 接口名+方法名String key = invokers.get(0).getUrl().getServiceKey() + "." + methodName;// invokers 的 hashcodeint identityHashCode = System.identityHashCode(invokers);// 查看缓存中是否存在对应 key 的数据,或 Invokers 列表是否有过变动。如果没有,则新添加到缓存中,并且返回负载均衡得出的 InvokerConsistentHashSelector<T> selector = (ConsistentHashSelector<T>) selectors.get(key);if (selector == null || selector.identityHashCode != identityHashCode) {selectors.put(key, new ConsistentHashSelector<T>(invokers, methodName, identityHashCode));selector = (ConsistentHashSelector<T>) selectors.get(key);}return selector.select(invocation);}// ConsistentHashSelector class ...
}

doSelect 中主要实现缓存检查和 Invokers 变动检查,一致性 hash 负载均衡的实现在这个内部类 ConsistentHashSelector 中实现。

private static final class ConsistentHashSelector<T> {// 存储虚拟节点private final TreeMap<Long, Invoker<T>> virtualInvokers;// 节点数private final int replicaNumber;// invoker 列表的 hashcode,用来判断 Invoker 列表是否变化private final int identityHashCode;// 请求中用来作Hash映射的参数的索引private final int[] argumentIndex;ConsistentHashSelector(List<Invoker<T>> invokers, String methodName, int identityHashCode) {this.virtualInvokers = new TreeMap<Long, Invoker<T>>();this.identityHashCode = identityHashCode;URL url = invokers.get(0).getUrl();// 获取节点数this.replicaNumber = url.getMethodParameter(methodName, HASH_NODES, 160);// 获取配置中的 参数索引String[] index = COMMA_SPLIT_PATTERN.split(url.getMethodParameter(methodName, HASH_ARGUMENTS, "0"));argumentIndex = new int[index.length];for (int i = 0; i < index.length; i++) {argumentIndex[i] = Integer.parseInt(index[i]);}for (Invoker<T> invoker : invokers) {// 获取 Invoker 中的地址,包括端口号String address = invoker.getUrl().getAddress();// 创建虚拟节点for (int i = 0; i < replicaNumber / 4; i++) {byte[] digest = md5(address + i);for (int h = 0; h < 4; h++) {long m = hash(digest, h);virtualInvokers.put(m, invoker);}}}}// 找出 Invokerpublic Invoker<T> select(Invocation invocation) {// 将参数转为字符串String key = toKey(invocation.getArguments());// 字符串参数转换为 md5byte[] digest = md5(key);// 根据 md5 找出 Invokerreturn selectForKey(hash(digest, 0));}// 将参数拼接成字符串private String toKey(Object[] args) {StringBuilder buf = new StringBuilder();for (int i : argumentIndex) {if (i >= 0 && i < args.length) {buf.append(args[i]);}}return buf.toString();}// 利用 md5 匹配到对应的 Invokerprivate Invoker<T> selectForKey(long hash) {// 找到第一个大于当前 hash 的 InvokerMap.Entry<Long, Invoker<T>> entry = virtualInvokers.ceilingEntry(hash);if (entry == null) {entry = virtualInvokers.firstEntry();}return entry.getValue();}// hash 运算private long hash(byte[] digest, int number) {return (((long) (digest[3 + number * 4] & 0xFF) << 24)| ((long) (digest[2 + number * 4] & 0xFF) << 16)| ((long) (digest[1 + number * 4] & 0xFF) << 8)| (digest[number * 4] & 0xFF))& 0xFFFFFFFFL;}// md5 运算private byte[] md5(String value) {MessageDigest md5;try {md5 = MessageDigest.getInstance("MD5");} catch (NoSuchAlgorithmException e) {throw new IllegalStateException(e.getMessage(), e);}md5.reset();byte[] bytes = value.getBytes(StandardCharsets.UTF_8);md5.update(bytes);return md5.digest();}}

一致 hash 实现过程就是先创建好虚拟节点,虚拟节点保存在 TreeMap 中。TreeMap 的 key 为配置的参数先进行 md5 运算,然后将 md5 值进行 hash 运算。TreeMap 的 value 为被选中的 Invoker。

最后请求时,计算参数的 hash 值,去从 TreeMap 中获取 Invoker。


ShortestResponseLoadBalance(最短响应时间算法)

ShortestResponseLoadBalance 是Dubbo 2.7 版本之后新增加的一个 LoadBalance 实现类。它实现了最短响应时间的负载均衡算法,也就是从多个 Provider 节点中选出调用成功的且响应时间最短的 Provider 节点,不过满足该条件的 Provider 节点可能有多个,所以还要再使用随机算法进行一次选择,得到最终要调用的 Provider 节点。

了解了 ShortestResponseLoadBalance 的核心原理之后,我们一起来看 ShortestResponseLoadBalance.doSelect() 方法的核心实现,如下所示:

public class ShortestResponseLoadBalance extends AbstractLoadBalance {public static final String NAME = "shortestresponse";@Overrideprotected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {// 记录Invoker集合的数量int length = invokers.size();// 用于记录所有Invoker集合中最短响应时间long shortestResponse = Long.MAX_VALUE;// 具有相同最短响应时间的Invoker个数int shortestCount = 0;// 存放所有最短响应时间的Invoker的下标int[] shortestIndexes = new int[length];// 存储每个Invoker的权重int[] weights = new int[length];// 存储权重总和int totalWeight = 0;// 记录第一个Invoker对象的权重int firstWeight = 0;// 最短响应时间Invoker集合中的Invoker权重是否相同boolean sameWeight = true;// Filter out all the shortest response invokersfor (int i = 0; i < length; i++) {Invoker<T> invoker = invokers.get(i);RpcStatus rpcStatus = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName());// 获取调用成功的平均时间,具体计算方式是:调用成功的请求数总数对应的总耗时 / 调用成功的请求数总数 = 成功调用的平均时间// RpcStatus 的内容在前面课时已经介绍过了,这里不再重复long succeededAverageElapsed = rpcStatus.getSucceededAverageElapsed();// 获取的是该Provider当前的活跃请求数,也就是当前正在处理的请求数int active = rpcStatus.getActive();// 计算一个处理新请求的预估值,也就是如果当前请求发给这个Provider,大概耗时多久处理完成long estimateResponse = succeededAverageElapsed * active;// 计算该Invoker的权重(主要是处理预热)int afterWarmup = getWeight(invoker, invocation);weights[i] = afterWarmup;// Same as LeastActiveLoadBalanceif (estimateResponse < shortestResponse) {// 第一次找到Invoker集合中最短响应耗时的Invoker对象,记录其相关信息shortestResponse = estimateResponse;shortestCount = 1;shortestIndexes[0] = i;totalWeight = afterWarmup;firstWeight = afterWarmup;sameWeight = true;} else if (estimateResponse == shortestResponse) {// 出现多个耗时最短的Invoker对象shortestIndexes[shortestCount++] = i;totalWeight += afterWarmup;if (sameWeight && i > 0&& afterWarmup != firstWeight) {sameWeight = false;}}}if (shortestCount == 1) {return invokers.get(shortestIndexes[0]);}// 如果耗时最短的所有Invoker对象的权重不相同,则通过加权随机负载均衡的方式选择一个Invoker返回if (!sameWeight && totalWeight > 0) {int offsetWeight = ThreadLocalRandom.current().nextInt(totalWeight);for (int i = 0; i < shortestCount; i++) {int shortestIndex = shortestIndexes[i];offsetWeight -= weights[shortestIndex];if (offsetWeight < 0) {return invokers.get(shortestIndex);}}}// 如果耗时最短的所有Invoker对象的权重相同,则随机返回一个return invokers.get(shortestIndexes[ThreadLocalRandom.current().nextInt(shortestCount)]);}
}

本文小结

本文学习了dubbo中常见的几种负载均衡算法的实现,可以多多学习其编码思维。在研究其代码时,需要仔细研究其实现原理,否则比较难懂其思想。

dubbo的负载均衡详解相关推荐

  1. Nginx代理功能与负载均衡详解

    序言 Nginx的代理功能与负载均衡功能是最常被用到的,关于nginx的基本语法常识与配置已在上篇文章中有说明,这篇就开门见山,先描述一些关于代理功能的配置,再说明负载均衡详细. Nginx代理服务的 ...

  2. Spring Cloud:使用Ribbon实现负载均衡详解(下)

    在上一篇文章(Spring Cloud:使用Ribbon实现负载均衡详解(上))中,我对 Ribbon 做了一个介绍,Ribbon 可以实现直接通过服务名称对服务进行访问.这一篇文章我详细分析一下如何 ...

  3. 大型网站系统架构系列:负载均衡详解(一)

    大型网站系统架构系列:负载均衡详解(一) 2016-03-20 架构说 面对大量用户访问.高并发请求,海量数据,可以使用高性能的服务器.大型数据库,存储设备,高性能Web服务器,采用高效率的编程语言比 ...

  4. Kubernetes上的负载均衡详解

    如果您的应用程序是面向大量用户.会吸引大量流量,那么一个不变的目标一定是在高效满足用户需求的同时.不让用户感知到任何类似于"服务器繁忙!"的情况.这一诉求的典型解决方案是横向扩展部 ...

  5. 大型网站架构系列:负载均衡详解(3)

    本次分享大纲 软件负载均衡概述 Ngnix负载均衡 Lvs负载均衡 Haproxy负载均衡 本次分享总结 一.软件负载均衡概述 硬件负载均衡性能优越,功能全面,但是价格昂贵,一般适合初期或者土豪级公司 ...

  6. 大型网站架构系列:负载均衡详解(4)

    原文:大型网站架构系列:负载均衡详解(4) 本文是负载均衡详解的第四篇,主要介绍了LVS的三种请求转发模式和八种负载均衡算法,以及Haproxy的特点和负载均衡算法.具体参考文章,详见最后的链接. 三 ...

  7. Tomcat安装、配置、优化及负载均衡详解

    博客园 首页 新随笔 联系 管理 订阅 随笔- 31  文章- 0  评论- 25  Tomcat安装.配置.优化及负载均衡详解 一.常见JavaWeb服务器      1.WebLogic:是BEA ...

  8. 转:大型网站架构系列:负载均衡详解(2)

    一.软件负载均衡概述 硬件负载均衡性能优越,功能全面,但是价格昂贵,一般适合初期或者土豪级公司长期使用.因此软件负载均衡在互联网领域大量使用.常用的软件负载均衡软件有Nginx,Lvs,HaProxy ...

  9. spring python负载均衡_Spring Cloud:使用Ribbon实现负载均衡详解(上)

    1. 什么是 Ribbon? Spring Cloud Ribbon 是一套实现客户端负载均衡的工具.注意是客户端,当然也有服务端的负载均衡工具,我们后面再介绍.可以认为 Ribbon 就是一个负载均 ...

最新文章

  1. 深入浅出的讲解傅里叶变换(完整)
  2. 毕设不会做,怎么办??
  3. 从开源小白到 Apache Member,我的成长之路
  4. 阿里员工都是这样排查Java问题的,附工具单
  5. JMeter接口测试通过企业微信API记录(二)创建审批接口并测试
  6. JavaScript实现MaximumSubarray最大子阵列(动态规划解决方案)算法(附完整源码)
  7. 基于实时深度学习的推荐系统架构设计和技术演进
  8. 结合源码看nginx-1.4.0之nginx内存管理详解
  9. BeginnersBook MongoDB 教程
  10. Hibernate组件作为复合主键(五)
  11. 张量分解在无线通信和MIMO雷达中的概述
  12. JS项目(制作随机选号页面)
  13. 流行技术产生的根源-阿朱吕建伟的个人观察
  14. 国家气象站逐日数据据v3.0 提取转成SWAT所需要格式
  15. 五线谱音名和组别对照表_五线谱上区别各个音的组别
  16. docker + nginx + uwsgi + ubuntu部署django项目
  17. 局域网电脑互访的设置大全
  18. 洛谷 P4218 [CTSC2010]珠宝商 后缀自动机+点分治
  19. Trainning 1 DAY
  20. 快桃科技居然给我无条件双倍工资!却又整天瞎搞

热门文章

  1. Golang 垃圾回收机制详解
  2. 纯净语音信号matlab,matlab语音信号处理
  3. Python3 高空抛物
  4. Xsolla采访《仙剑5前传之心愿》
  5. 数据分析(8)--matplotlib 数据可视化
  6. 将Python文件编译为exe可执行程序
  7. Git:工作原理(核心)
  8. 咪咕圈圈首创线上应援空间新玩法 为动感地带明星合伙人张艺兴花式庆生太圈粉
  9. JSP实现登录和注册操作
  10. github使用教程(二)【保姆级】{github desktop教程,github desktop的安装和配置}