dubbo的负载均衡策略之RandomLoadBalance加权随机策略源码分析

背景

剖析dubbo源码,发现dubbo提供了五种负载均衡策略,分别是

  • 一致性Hash平衡策略:ConsistentHashLoadBalance
  • 加权随机策略:RandomLoadBalance
  • 最少活跃策略:LeastActiveLoadBalance
  • 加权轮询策略:RoundRobinLoadBalance
  • 最短响应时间策略:ShortestResponseLoadBalance

dubbo的负载均衡策略之RandomLoadBalance加权随机策略源码分析

现在先分析最简单的加权平均策略——RandomLoadBalance

源码概览

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.rpc.cluster.loadbalance;

import org.apache.dubbo.common.URL;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;

import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

/**
* This class select one provider from multiple providers randomly.
* You can define weights for each provider:
* If the weights are all the same then it will use random.nextInt(number of invokers).
* If the weights are different then it will use random.nextInt(w1 + w2 + ... + wn)
* Note that if the performance of the machine is better than others, you can set a larger weight.
* If the performance is not so good, you can set a smaller weight.
*/
public class RandomLoadBalance extends AbstractLoadBalance {

public static final String NAME = "random";

/**
* Select one invoker between a list using a random criteria
* @param invokers List of possible invokers
* @param url URL
* @param invocation Invocation
* @param <T>
* @return The selected invoker
*/
@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 maxWeight of every invokers, the minWeight = 0 or the maxWeight of the last invoker
int[] weights = new int[length];
// The sum of weights
int totalWeight = 0;
for (int i = 0; i < length; i++) {
int weight = getWeight(invokers.get(i), invocation);
// Sum
totalWeight += weight;
// save for later use
weights[i] = totalWeight;
if (sameWeight && totalWeight != weight * (i + 1)) {
sameWeight = false;
}
}
// 带权重的随机算法
if (totalWeight > 0 && !sameWeight) {
// If (not every invoker has the same weight & at least one invoker's weight>0), select randomly based on totalWeight.
// 如果权重之和大于0,且权重不一样,就将权重之和作为产生随机数的bound
int offset = ThreadLocalRandom.current().nextInt(totalWeight);
// Return a invoker based on the random value.
for (int i = 0; i < length; i++) {
if (offset < weights[i]) {// 权重越大,越容易被取到
return invokers.get(i);
}
}
}
// If all invokers have the same weight value or totalWeight=0, return evenly.
// 如果没设置权重,或者权重相同,就随机返回一个服务提供者
return invokers.get(ThreadLocalRandom.current().nextInt(length));
}

}

类说明

/**
* This class select one provider from multiple providers randomly.
* You can define weights for each provider:
* If the weights are all the same then it will use random.nextInt(number of invokers).
* If the weights are different then it will use random.nextInt(w1 + w2 + ... + wn)
* Note that if the performance of the machine is better than others, you can set a larger weight.
* If the performance is not so good, you can set a smaller weight.
*/

这个类的作用是从多个服务提供者中随机选择一个

你可以为每一个服务提供者设置权重

如果所有的权重都是一样的,将使用random.nextInt(服务列表的个数),也就是从服务列表中随机取一个

如果权重不一样,就使用random.nextInt(服务权重之和)

温馨提示:如果你的某台机器性能更好,可以把它的权重设置的大一点,如果哪台机器的性能不是很好,权重就设置低一点。

源码剖析

RandomLoadBalance就一个方法,doSelect,参数中传进来候选服务列表以及URL还有调用封装对象,前面的都比较简单,循环遍历invokers,获取服务权重,然后累加起来,值得一看的是这个getWeight方法

dubbo的负载均衡策略之RandomLoadBalance加权随机策略源码分析

获取权重


/**
* Get the weight of the invoker's invocation which takes warmup time into account
* if the uptime is within the warmup time, the weight will be reduce proportionally
*
* 获取服务的权重,考虑预热因素,如果服务正在预热中,权重相应的减少
*
* @param invoker the invoker
* @param invocation the invocation of this invoker
* @return weight
*/
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);
}

这个获取权重的方法值得说道说道,它上面注释的意思是:

获取服务的权重,考虑预热因素,如果服务正在预热中,权重相应的减少

预热的背景是:如果一个服务刚启动,还未完成预热,这个时候如果大量请求直接打在这个服务上,很可能压垮这个服务,所以在预热完成之前,流量要慢慢的切过来,那应该多慢,怎么切呢?dubbo巧妙的通过calculateWarmupWeight来完成这个过程。

    /**
* Calculate the weight according to the uptime proportion of warmup time
* the new weight will be within 1(inclusive) to weight(inclusive)
*
* @param uptime the uptime in milliseconds
* @param warmup the warmup time in milliseconds
* @param weight the weight of an invoker
* @return weight which takes warmup into account
*/
static int calculateWarmupWeight(int uptime, int warmup, int weight) {
int ww = (int) ( uptime / ((float) warmup / weight));
return ww < 1 ? 1 : (Math.min(ww, weight));
}

这个计算预热时权重有三个参数,分别是uptime(服务启动时的时间)、warmup(预热时长)、weight (权重),而且从可以看出,这个预热时长和权重是有默认值的

dubbo的负载均衡策略之RandomLoadBalance加权随机策略源码分析

权重默认100

int DEFAULT_WEIGHT = 100;

预热时长默认10分钟

int DEFAULT_WARMUP = 10 * 60 * 1000;

在默认参数的情况下,基于下面这个公式,我们可以知道,服务启动后,每隔6秒钟,服务的权重就会增加1,直到十分钟后,也就是600秒时,服务的权重达到100

 int ww = (int) ( uptime / ((float) warmup / weight))

不得不说,有点巧妙!

获取完权重之后,就是进行加权随机了,这里的小细节就在代码里,细品一下就能品出来,它是如何做到在随机的情况下,如何让权重越大的,获得更多的“随机”机会的。

      // 带权重的随机算法
if (totalWeight > 0 && !sameWeight) {
// If (not every invoker has the same weight & at least one invoker's weight>0), select randomly based on totalWeight.
// 如果权重之和大于0,且权重不一样,就将权重之和作为产生随机数的bound
int offset = ThreadLocalRandom.current().nextInt(totalWeight);
// Return a invoker based on the random value.
for (int i = 0; i < length; i++) {
if (offset < weights[i]) {// 权重越大,越容易被取到
return invokers.get(i);
}
}
}

另一个小细节就是ThreadLocalRandom,为什么是ThreadLocalRandom,而不是Random

ThreadLocalRandom

答案就在ThreadLocalRandom类的描述里

A random number generator isolated to the current thread. Like the global Random generator used by the Math class, a ThreadLocalRandom is initialized with an internally generated seed that may not otherwise be modified. When applicable, use of ThreadLocalRandom rather than shared Random objects in concurrent programs will typically encounter much less overhead and contention. Use of ThreadLocalRandom is particularly appropriate when multiple tasks (for example, each a ForkJoinTask) use random numbers in parallel in thread pools.

大致意思就是, ThreadLocalRandom和java.util.Random类似,都是返回一个随机数,但在并发场景下ThreadLocalRandom开销更小,不会发生线程竞争,所以在多线程情况下推荐使用ThreadLocalRandom,比如像ForkJoinTask线程池等。

总结

RandomLoadBalance代码阅读起来没什么障碍,但里面包含的小细节(思想)还是很值得学习的,多阅读优秀的代码,模仿它,学习它,超越它!

发表评论

相关文章