Dubbo源码分析十三、负载均衡

本文深入剖析了Dubbo的五种负载均衡策略:Random、LeastActive、ShortestResponse、RoundRobin和ConsistentHash,详细解释了每种策略的原理和实现,包括加权随机、最小活跃数、最短响应时间、加权轮询和一致性哈希算法。通过对算法思想的解析和代码分析,帮助读者理解如何在实际业务中选择合适的负载均衡策略。
摘要由CSDN通过智能技术生成

dubbo提供了5种负载均衡策略,分别是:

  • RandomLoadBalance --加权随机
  • LeastActiveLoadBalance --最小活跃数
  • ShortestResponseLoadBalance --最短响应时间
  • RoundRobinLoadBalance --加权轮询
  • ConsistentHashLoadBalance --一致性hash

首先我们知道在集群ClusterInvoker选择Invoker时调用到LoadBanlance的select方法,我们先看一下:

@Override
public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) {
    if (CollectionUtils.isEmpty(invokers)) {
        return null;
    }
    // 如果 invokers 列表中仅有一个 Invoker,直接返回即可,无需进行负载均衡
    if (invokers.size() == 1) {
        return invokers.get(0);
    }

    // 调用 doSelect 方法进行负载均衡,该方法为抽象方法,由子类实现
    return doSelect(invokers, url, invocation);
}

这个是AbstractLoadBalance的实现,如果备选列表中只有一个直接取出来返回,否则调用模板方法doSelect,由子类实现。下面会详细分析每个子类的doSelect方法。

AbstractLoadBalance 除了实现了 LoadBalance 接口方法,还封装了一些公共逻辑,比如服务提供者权重计算逻辑。

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 {
        // 从 url 中获取权重 weight 配置值
        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;
                }
                // 获取服务预热时间,默认为10分钟
                int warmup = invoker.getUrl().getParameter(WARMUP_KEY, DEFAULT_WARMUP);
                // 如果服务运行时间小于预热时间,则重新计算服务权重,即降权
                if (uptime > 0 && uptime < warmup) {
                    // 重新计算服务权重。 (启动时间/预热时间)*权重  启动时间越长,前面比值越接近1
                    // 当启动时间小于预热时间时,实际上是对权重做降权处理。
                    weight = calculateWarmupWeight((int)uptime, warmup, weight);
                }
            }
        }
    }
    return Math.max(weight, 0);
}

static int calculateWarmupWeight(int uptime, int warmup, int weight) {
	// 计算权重,下面代码逻辑上形似于 (uptime / warmup) * weight。
	// 随着服务运行时间 uptime 增大,权重计算值 ww 会慢慢接近配置值 weight
	int ww = (int) ( uptime / ((float) warmup / weight));
	return ww < 1 ? 1 : (Math.min(ww, weight));
}

我们看到,计算权重时并不是直接取的用户定义的权重值,而是根据启动时间慢慢接近到配置的权重值。这个时间默认是10分钟。这样做的好处是对于刚启动的服务可以有效防止瞬时流量过大。起到服务预热的作用。

RandomLoadBalance

RandomLoadBalance 是默认的负载策略。

先引入一段官网上关于加权随机算法的算法思想:

假设我们有一组服务器 servers = [A, B, C],他们对应的权重为 weights = [5, 3, 2],权重总和为10。现在把这些权重值平铺在一维坐标值上,[0, 5) 区间属于服务器 A,[5, 8) 区间属于服务器 B,[8, 10) 区间属于服务器 C。接下来通过随机数生成器生成一个范围在 [0, 10) 之间的随机数,然后计算这个随机数会落到哪个区间上。比如数字3会落到服务器 A 对应的区间上,此时返回服务器 A 即可。权重越大的机器,在坐标轴上对应的区间范围就越大,因此随机数生成器生成的数字就会有更大的概率落到此区间内。只要随机数生成器产生的随机数分布性很好,在经过多次选择后,每个服务器被选中的次数比例接近其权重比例。比如,经过一万次选择后,服务器 A 被选中的次数大约为5000次,服务器 B 被选中的次数约为3000次,服务器 C 被选中的次数约为2000次。

很巧妙的一种方法。

@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
    // Number of invokers
    // invokers的数量
    int length = invokers.size();
    // Every invoker has the same weight?
    // 是否具有相同的权重
    boolean sameWeight = true;
    // the weight of every invokers
    // 各个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;
    // 下面这个循环有两个作用,第一是计算总权重 totalWeight,
    // 第二是检测每个服务提供者的权重是否相同
    for (int i = 1; i < length; i++) {
        int weight = getWeight(invokers.get(i), invocation);
        // save for later use
        // 第N个权重赋值
        weights[i] = weight;
        // Sum
        // 累加权重
        totalWeight += weight;
        // 检测当前服务提供者的权重与第一个服务提供者的权重是否相同,
        // 不相同的话,则将 sameWeight 置为 false。
        if (sameWeight && weight != firstWeight) {
            sameWeight = false;
        }
    }
    // 下面的 if 分支主要用于获取随机数,并计算随机数落在哪个区间上
    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, totalWeight) 区间内的数字
        int offset = ThreadLocalRandom.current().nextInt(totalWeight);
        // Return a invoker based on the random value.
        // 循环让 offset 数减去服务提供者权重值,当 offset 小于0时,返回相应的 Invoker。
        // 举例说明一下,我们有 servers = [A, B, C],
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值