加权随机算法
随机算法
随机算法的实现比较简单,只需要从所有服务中随机选择一个即可。代码如下:
private static final List<String> SERVERS;
static {
SERVERS = Lists.newArrayList("192.168.0.1", "192.168.0.2", "192.168.0.3");
}
private static String doSelect() {
Random random = new Random();
int offset = random.nextInt(SERVERS.size());
return SERVERS.get(offset);
}
但是单纯的随机算法可能面临的一个问题是:假设我们有三台服务器,SERVER1是1核2G的配置,SERVER2是2核4G的配置,SERVER3是8核64G的配置,那么他们的服务能力是不一样的,但是我们目前的算法却几乎是平均分配请求的,这就导致SERVER1处理较慢,而SERVER3又不能充分发挥他的性能,因此我们需要加一个权重,让更有能力的SERVER3去做更多的事,实现能者多劳。
加权随机
假设我们服务端有三台机器servers = ["server1", "server2", "server3"]
,其中每台服务器的权重为:weights = [6, 3, 1]
,权重总和为10,我们则需要设计一套算法,能保证请求被转发到server1的概率趋近于6/10, 被转发到server2的概率趋近于3/10, 被转发到server3的概率趋近于1/10。
如何实现呢,最简单的办法是我们可以按照权重复制server信息,比如初始化一个length为10的server数组,将权重为6的server1复制6份保存在数组中,将权重为3的server2复制3份保存在数组中。在通过随机数生成一个介于0-10的下标,从数组中取server信息。这样实现有一个最大的问题就是需要耗费太多额外的内存来保存重复的数据。
还有一种算法是:如果权重是10,那么我们建一个一维的坐标轴,标记0-9十个节点,然后生成一个随机数,如果在[0,5)的区间之内,则选择server1,如果在[6-8)的区间内则选择server2,如果在[9,10)的区间内,则选择server3。接下来我们一起看看dubbo中是如何通过代码实现的。
/**
* 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();
if (!needWeightLoadBalance(invokers,invocation)){
return invokers.get(ThreadLocalRandom.current().nextInt(length));
}
// 是否每个server都有一样的权重
boolean sameWeight = true;
// 用来模拟我们说的坐标轴的区间范围,下标对应了invokers中每个invoker的下标,具体保存的值则是其在X轴上的右边界
// ex: 以前面6、4、1的数据为例,则weights的数据为:weights: [6,9,10]
int[] weights = new int[length];
int totalWeight = 0;
// 遍历所有的server,对上面三个重要的参数赋值
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.
int offset = ThreadLocalRandom.current().nextInt(totalWeight);
// Return a invoker based on the random value.
// 遍历服务列表,生成一个介于0-权重总和之间的随机数,如果这个随机数落在服务对应的区间,则选中他,否则继续判断下一个
// ex: 第一次遍历到server1,是否生成的随机数在serve1的区间[0,6),是则选中,否则继续遍历,取到SERVER2,重复上述步骤
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));
}