1. 前言
前面的文章已经分析了,Dubbo在做集群调用时,Directory会先根据Router做路由规则过滤,最后幸存下来的Invoker才会对它们做负载均衡。
LoadBalance是Dubbo提供的负载均衡接口,截止2.7.8版本,Dubbo内置了五种负载均衡策略。LoadBalance也是可以通过SPI加载的,开发者可以很方便的扩展自定义的负载均衡策略。
2. 源码分析
LoadBalance接口定义十分简单,它只有一个功能,从一组Invoke里选出一个最终等待被调用的Invoker。
@SPI(RandomLoadBalance.NAME)
public interface LoadBalance {
@Adaptive("loadbalance")
<T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;
}
LoadBalance只负责实现负载均衡算法,Dubbo并没有直接使用它,而是在AbstractClusterInvoker#select()对它做了一层包装,包装的目的是为了实现以下功能:
- 粘滞连接。
- 连接可用性检测。
- 失败服务规避。
AbstractClusterInvoker#select()会在负载均衡之前处理粘滞连接的问题,「粘滞连接」是Dubbo的一个特性,适用于有状态的服务,尽可能的让消费者总是向同一个提供者发起调用,除非该提供者挂了。
protected Invoker<T> select(LoadBalance loadbalance, Invocation invocation,
List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException {
String methodName = invocation == null ? StringUtils.EMPTY_STRING : invocation.getMethodName();
// 是否开启粘滞连接?
boolean sticky = invokers.get(0).getUrl()
.getMethodParameter(methodName, CLUSTER_STICKY_KEY, DEFAULT_CLUSTER_STICKY);
if (stickyInvoker != null && !invokers.contains(stickyInvoker)) {
stickyInvoker = null;
}
if (sticky && stickyInvoker != null && (selected == null || !selected.contains(stickyInvoker))) {
if (availablecheck && stickyInvoker.isAvailable()) {
return stickyInvoker;
}
}
Invoker<T> invoker = doSelect(loadbalance, invocation, invokers, selected);
if (sticky) {
stickyInvoker = invoker;
}
return invoker;
}
紧接着,AbstractClusterInvoker#doSelect()方法会进行负载均衡,选出的Invoker首先会判断之前是否已经调用过,服务重试时会规避掉之前已经失败的服务,然后再对Invoker做连接可用性检测,以此为依据判断是否需要重新负载。
private Invoker<T> doSelect(LoadBalance loadbalance, Invocation invocation,
List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException {
if (invokers.size() == 1) {
return invokers.get(0);
}
// 负载均衡选出Invoker
Invoker<T> invoker = loadbalance.select(invokers, getUrl(), invocation);
/**
* 判断是否需要重新负载均衡
* 1.选出的Invoker已经被使用过
* 2.连接不可用且开启连接可用性检测
*/
if ((selected != null && selected.contains(invoker))
|| (!invoker.isAvailable() && getUrl() != null && availablecheck)) {
try {
// 重新负载
Invoker<T> rInvoker = reselect(loadbalance, invocation, invokers, selected, availablecheck);
if (rInvoker != null) {
invoker = rInvoker;
} else {
int index = invokers.indexOf(invoker);
try {
invoker = invokers.get((index + 1) % invokers.size());
} catch (Exception e) {
logger.warn(e.getMessage() + " may because invokers list dynamic change, ignore.", e);
}
}
} catch (Throwable t) {
logger.error("cluster reselect fail reason is :" + t.getMessage() + " if can not solve, you can set cluster.availablecheck=false in url", t);
}
}
return invoker;
}
上述是Dubbo调用LoadBalance之前的包装处理,接下来我们看看原始的LoadBalance是如何实现的。
截止2.7.8版本,Dubbo内置了五种负载均衡策略:
| 策略 | 说明 |
|---|---|
| Random | 加权随机,默认算法 |
| RoundRobin | 加权轮询 |
| LeastActive | 最少活跃调用数,慢的提供者会接收到更少的请求 |
| ConsistentHash | 一致性哈希,相同参数请求同一提供者 |
| ShortestResponse | 最快响应,选出响应时间最短的提供者 |
类的继承关系如下图:
抽象类的
select()方法很简单,只是判断如果提供者就一个的话,直接返回,因为没得选。提供者有多个,才会调用子类的doSelect()方法。
public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) {
if (CollectionUtils.isEmpty(invokers)) {
return null;
}
if (invokers.size() == 1) {
// 没得选
return invokers.get(0);
}
// 调用子类负载均衡算法
return doSelect(invokers, url, invocation);
}
RandomLoadBalance是Dubbo默认的负载均衡策略,我们以它为例分析下,其他算法会在后面的文章分析。
RandomLoadBalance采用加权随机算法,理论上来说,权重越大的提供者,被随机到的概率也就越大,参数weight用来设置权重。
加权随机的算法过程:
- 判断所有提供者权重是否相同,计算总权重。
- 权重相同,直接对数量做随机,返回结束。
- 权重不同,对总权重随机一个偏移量offset。
- 根据offset落在权重数组的位置,返回提供者。
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
// 提供者数量
int length = invokers.size();
// 是否每个提供者权重都相同?
boolean sameWeight = true;
// 权重
int[] weights = new int[length];
// 第0个提供者的权重
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);
// save for later use
weights[i] = weight;
// Sum
totalWeight += weight;
if (sameWeight && weight != firstWeight) {
sameWeight = false;
}
}
if (totalWeight > 0 && !sameWeight) {
// 权重不同的情况,加权随机
// 对总权重随机一个偏移量
int offset = ThreadLocalRandom.current().nextInt(totalWeight);
// 看看偏移量落在数组的哪个位置
for (int i = 0; i < length; i++) {
offset -= weights[i];
if (offset < 0) {
return invokers.get(i);
}
}
}
// 权重都相同的情况,直接随机
return invokers.get(ThreadLocalRandom.current().nextInt(length));
}
下面这张图,描述了这个过程:
3. 总结
LoadBalance是Dubbo提供的负载均衡接口,截止2.7.8版本Dubbo内置了五种负载均衡策略,Random加权随机是默认的策略。 Dubbo并没有直接使用LoadBalance,而是在AbstractClusterInvoker包装了一层,包装的目的是为了实现粘滞连接特性、Invoker连接可用性检测、避免Invoker重复调用等功能。