Dubbo是一个分布式服务框架,能避免单点故障和支持服务的横向扩容。一个服务一般会部署多个实例。如何从多个服务 Provider 组成的集群中挑选出一个进行调用,就涉及到一个负载均衡的策略。html
在讨论负载均衡以前,我想先解释一下这3个概念。node
这3个概念容易混淆。他们都描述了怎么从多个 Provider 中选择一个来进行调用。那他们到底有什么区别呢?下面我来举一个简单的例子,把这几个概念阐述清楚吧。算法
有一个Dubbo的用户服务,在北京部署了10个,在上海部署了20个。一个杭州的服务消费方发起了一次调用,而后发生了如下的事情:数据库
上面的第1,2,4步骤就分别对应了路由,负载均衡和集群容错。 Dubbo中,先经过路由,从多个 Provider 中按照路由规则,选出一个子集。再根据负载均衡从子集中选出一个 Provider 进行本次调用。若是调用失败了,根据集群容错策略,进行重试或定时重发或快速失败等。 能够看到Dubbo中的路由,负载均衡和集群容错发生在一次RPC调用的不一样阶段。最早是路由,而后是负载均衡,最后是集群容错。 本文档只讨论负载均衡,路由和集群容错在其余的文档中进行说明。apache
Dubbo内置了4种负载均衡策略:缓存
顾名思义,随机负载均衡策略就是从多个 Provider 中随机选择一个。可是 Dubbo 中的随机负载均衡有一个权重的概念,即按照权重设置随机几率。好比说,有10个 Provider,并非说,每一个 Provider 的几率都是同样的,而是要结合这10个 Provider 的权重来分配几率。服务器
Dubbo中,能够对 Provider 设置权重。好比机器性能好的,能够设置大一点的权重,性能差的,能够设置小一点的权重。权重会对负载均衡产生影响。能够在Dubbo Admin中对 Provider 进行权重的设置。网络
基于权重的负载均衡算法负载均衡
随机策略会先判断全部的 Invoker 的权重是否是同样的,若是都是同样的,那么处理就比较简单了。使用random.nexInt(length)就能够随机生成一个 Invoker 的序号,根据序号选择对应的 Invoker 。若是没有在Dubbo Admin中对服务 Provider 设置权重,那么全部的 Invoker 的权重就是同样的,默认是100。 若是权重不同,那就须要结合权重来设置随机几率了。算法大概以下: 假若有4个 Invoker。框架
invoker | weight |
---|---|
A | 10 |
B | 20 |
C | 20 |
D | 30 |
A,B,C和D总的权重是10 + 20 + 20 + 30 = 80。将80个数分布在以下的图中:
+-----------------------------------------------------------------------------------+ | | | | | +-----------------------------------------------------------------------------------+ 1 10 30 50 80 |-----A----|---------B----------|----------C---------|---------------D--------------| ---------------------15 -------------------------------------------37 -----------------------------------------------------------54
上面的图中一共有4块区域,长度分别是A,B,C和D的权重。使用random.nextInt(10 + 20 + 20 + 30),从80个数中随机选择一个。而后再判断该数分布在哪一个区域。好比,若是随机到37,37是分布在C区域的,那么就选择 Invoker C。15是在B区域,54是在D区域。
随机负载均衡源码
(有权重:在权重和的范围内生成一个随机数,遍历invoker,用权重和循环减去invoker的权重,结果小于0时的invoker被选中)
下面是随机负载均衡的源码,为了方便阅读和理解,我把无关部分都去掉了。
public class RandomLoadBalance extends AbstractLoadBalance { private final Random random = new Random(); protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) { int length = invokers.size(); // Invoker 总数 int totalWeight = 0; // 全部 Invoker 的权重的和 // 判断是否是全部的 Invoker 的权重都是同样的 // 若是权重都同样,就简单了。直接用Random生成索引就能够了。 boolean sameWeight = true; for (int i = 0; i < length; i++) { int weight = getWeight(invokers.get(i), invocation); totalWeight += weight; // Sum if (sameWeight && i > 0 && weight != getWeight(invokers.get(i - 1), invocation)) { sameWeight = false; } } if (totalWeight > 0 && !sameWeight) { // 若是不是全部的 Invoker 权重都相同,那么基于权重来随机选择。权重越大的,被选中的几率越大 int offset = random.nextInt(totalWeight); for (int i = 0; i < length; i++) { offset -= getWeight(invokers.get(i), invocation); if (offset < 0) { return invokers.get(i); } } } // 若是全部 Invoker 权重相同 return invokers.get(random.nextInt(length)); } }
轮询负载均衡,就是依次的调用全部的 Provider。和随机负载均衡策略同样,轮询负载均衡策略也有权重的概念。 轮询负载均衡算法可让RPC调用严格按照咱们设置的比例来分配。不论是少许的调用仍是大量的调用。可是轮询负载均衡算法也有不足的地方,存在慢的 Provider 累积请求的问题,好比:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,长此以往,全部请求都卡在调到第二台上,致使整个系统变慢。
官方解释:
最少活跃调用数,相同活跃数的随机,活跃数指调用先后计数差,使慢的机器收到更少。
这个解释好像说的不是太明白。目的是让更慢的机器收到更少的请求,但具体怎么实现的仍是不太清楚。举个例子:每一个服务维护一个活跃数计数器。当A机器开始处理请求,该计数器加1,此时A还未处理完成。若处理完毕则计数器减1。而B机器接受到请求后很快处理完毕。那么A,B的活跃数分别是1,0。当又产生了一个新的请求,则选择B机器去执行(B活跃数最小),这样使慢的机器A收到少的请求。
处理一个新的请求时,Consumer 会检查全部 Provider 的活跃数,若是具备最小活跃数的 Invoker 只有一个,直接返回该 Invoker:
if (leastCount == 1) { // 若是只有一个最小则直接返回 return invokers.get(leastIndexs[0]); }
若是最小活跃数的 Invoker 有多个,且权重不相等同时总权重大于0,这时随机生成一个权重,范围在 (0,totalWeight) 间内。最后根据随机生成的权重,来选择 Invoker。
if (! sameWeight && totalWeight > 0) { // 若是权重不相同且权重大于0则按总权重数随机 int offsetWeight = random.nextInt(totalWeight); // 并肯定随机值落在哪一个片段上 for (int i = 0; i < leastCount; i++) { int leastIndex = leastIndexs[i]; offsetWeight -= getWeight(invokers.get(leastIndex), invocation); if (offsetWeight <= 0) return invokers.get(leastIndex); } }
概述:
若是采用经常使用的hash(object)%N算法,那么在有机器添加或者删除后,映射关系就变了,不少原有的缓存就没法找到了
一致性hash:添加删除机器先后映射关系一致,固然,不是严格一致。实现的关键是环形Hash空间。将数据和机器都hash到环上,数据映射到顺时针离本身最近的机器中。
一致性hash单调性体如今:
不管是新增主机仍是删除主机,被影响的都是离那台主机最近的那些节点,其余节点映射关系没有影响
使用一致性 Hash 算法,让相同参数的请求老是发到同一 Provider。 当某一台 Provider 崩溃时,本来发往该 Provider 的请求,基于虚拟节点,平摊到其它 Provider,不会引发剧烈变更。 参见我另外一篇:https://www.cnblogs.com/twoheads/p/10135896.html
缺省只对第一个参数Hash,若是要修改,请配置:
<dubbo:parameter key="hash.arguments" value="0,1" />
缺省用160份虚拟节点,若是要修改,请配置:
<dubbo:parameter key="hash.nodes" value="320" />
优势:一致性Hash算法能够和缓存机制配合起来使用。好比有一个服务getUserInfo(String userId)。设置了Hash算法后,相同的userId的调用,都会发送到同一个 Provider。这个 Provider 上能够把用户数据在内存中进行缓存,减小访问数据库或分布式缓存的次数。若是业务上容许这部分数据有一段时间的不一致,能够考虑这种作法。减小对数据库,缓存等中间件的依赖和访问次数,同时减小了网络IO操做,提升系统性能。
若是不指定负载均衡,默认使用随机负载均衡。咱们也能够根据本身的须要,显式指定一个负载均衡。 能够在多个地方类来配置负载均衡,好比 Provider 端,Consumer端,服务级别,方法级别等。
<dubbo:service interface="..." loadbalance="roundrobin" />
该服务的全部方法都使用roundrobin负载均衡。
<dubbo:reference interface="..." loadbalance="roundrobin" />
该服务的全部方法都使用roundrobin负载均衡。
<dubbo:service interface="..."> <dubbo:method name="hello" loadbalance="roundrobin"/> </dubbo:service>
只有该服务的hello方法使用roundrobin负载均衡。
<dubbo:reference interface="..."> <dubbo:method name="hello" loadbalance="roundrobin"/> </dubbo:reference>
只有该服务的hello方法使用roundrobin负载均衡。
和Dubbo其余的配置相似,多个配置是有覆盖关系的:
因此,上面4种配置的优先级是:
Dubbo的4种负载均衡的实现,大多数状况下能知足要求。有时候,由于业务的须要,咱们可能须要实现本身的负载均衡策略。本章只说明如何配置负载均衡算法。关于Dubbo扩展机制的更多内容,请前往Dubbo可扩展机制实战。
略
转自dubbo官网:
http://dubbo.apache.org/zh-cn/blog/dubbo-loadbalance.html