基于dubbo v2.7.8
博主在学习自己部门的 rpc 框架时,发现在负载均衡算法上,其参考了 dubbo 的 RandomLoadBalance
算法,故对 dubbo 源码进行学习。
dubbo 提供了 RandomLoadBalance
的负载均衡算法,该算法会从服务列表中,随机选择出一个 invoker,来处理后续的请求。而对于随机选择上,dubbo 采用权重计算随机的方式。
代码具体思路:
- 计算每一个 invoker 的权重
- 判断是否每一个 invoker 的权重相同,如果所有 inovker 拥有相同的权重,则均匀的返回 invoker
- 如果权重有不同,则按照权重,随机获取 invoker
RandomLoadBalance.class:
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
// Number of invokers
int length = invokers.size();
// Every invoker has the same weight?
boolean sameWeight = true;
// the weight of every invokers
int[] weights = new int[length];
// the first invoker's weight
// 获取第一个 invoker 的权重
int firstWeight = getWeight(invokers.get(0), invocation);
weights[0] = firstWeight;
// The sum of weights
int totalWeight = firstWeight;
// 计算所有 invoker 的和,并判断是否存在不同的权重
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;
}
}
// 如果存在不同的权重,则通过权重获取随机数,来随机获取 invoker
if (totalWeight > 0 && !sameWeight) {
// If (not every invoker has the same weight & at least one invoker's weight>0), select randomly based on totalWeight.
int offset = ThreadLocalRandom.current().nextInt(totalWeight);
// Return a invoker based on the random value.
for (int i = 0; i < length; i++) {
offset -= weights[i];
if (offset < 0) {
return invokers.get(i);
}
}
}
// If all invokers have the same weight value or totalWeight=0, return evenly.
return invokers.get(ThreadLocalRandom.current().nextInt(length));
}
第二部分中,根据权重值随机获取 invoker 的算法非常巧妙,其先求得将所有 invoker 的权重之和,再随机获取0到权重和之间的整数(包含0,不包含权重和),再用该整数,按 invoker 的顺序,减去 invoker 的权重值,但减出来的结果小于 0 时,以为着对应的 invoker,即为被随机选中的 invoker,从概率上分析,刚好实现了按照权重进行随机的请求。
而关于权重的计算问题,dubbo 采用的是通过计算预热时间来获取。
默认的权重为100,默认的预热时间为 10min,如果服务注册到注册中心到现在的时间差,不小于预热时间,则直接返回权重100,如时间差小于预热时间,则按 (时间差/预热时间)*权重
的方式进行计算。
如采用默认的预热时间,当服务启动 5min时,其权重为:50
具体代码:
int DEFAULT_WEIGHT = 100;
int DEFAULT_WARMUP = 10 * 60 * 1000;
int getWeight(Invoker<?> invoker, Invocation invocation) {
int weight;
URL url = invoker.getUrl();
// Multiple registry scenario, load balance among multiple registries.
if (REGISTRY_SERVICE_REFERENCE_PATH.equals(url.getServiceInterface())) {
weight = url.getParameter(REGISTRY_KEY + "." + WEIGHT_KEY, DEFAULT_WEIGHT);
} else {
weight = url.getMethodParameter(invocation.getMethodName(), WEIGHT_KEY, DEFAULT_WEIGHT);
if (weight > 0) {
long timestamp = invoker.getUrl().getParameter(TIMESTAMP_KEY, 0L);
if (timestamp > 0L) {
long uptime = System.currentTimeMillis() - timestamp;
if (uptime < 0) {
return 1;
}
int warmup = invoker.getUrl().getParameter(WARMUP_KEY, DEFAULT_WARMUP);
if (uptime > 0 && uptime < warmup) {
weight = calculateWarmupWeight((int)uptime, warmup, weight);
}
}
}
}
return Math.max(weight, 0);
}
static int calculateWarmupWeight(int uptime, int warmup, int weight) {
int ww = (int) ( uptime / ((float) warmup / weight));
return ww < 1 ? 1 : (Math.min(ww, weight));
}
使用 RandomLoadBalance
的负载均衡算法,可以使用后端服务在刚启动的一段时间,不会一瞬间获取过多的负载,而是根据启动时间,慢慢流量增加,这给刚启动的后端服务提供时间进行预热,缓解服务刚启动时因性能不足而造成处理请求失败的情况。