Dubbo常用LoadBalance浅析-RoundRobin & Random

870 阅读2分钟

1 获取权重

Dubbo中的LoadBalance,都是实现了LoadBalance接口,并继承了抽象类AbstractLoadBalance

LoadBalance中的权重,是预热选择后的权重,通过AbstractLoadBalance.getWeight获取。

在预热期内,权重逐步从1恢复到设置的值。

  • 默认的权重值=100;
  • 默认的预热时间=10分钟。
    /* 
    如果uptime在预热时间内,则按比例降低权重;
    目的流量恢复时,可以逐步的恢复。
     */
    int getWeight(Invoker<?> invoker, Invocation invocation) {

        // WEIGHT_KEY = "weight",  DEFAULT_WEIGHT = 100
        int weight = invoker.getUrl().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;
                }
                // WARMUP_KEY = "warmup",   DEFAULT_WARMUP = 10 * 60 * 1000 (10分钟)
                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);
    }
    
    // 根据uptime和warmup的比例取weight.
    static int calculateWarmupWeight(int uptime, int warmup, int weight) {
        int ww = (int) ( uptime / ((float) warmup / weight));
        return ww < 1 ? 1 : (Math.min(ww, weight));
    }

2 RoundRobinLoadBalance

2.1 WeightedRoundRobin

WeightedRoundRobin是RoundRobin中,各invoker的权重配置对象。

 protected static class WeightedRoundRobin {
        
        // 配置的权重值,从配置中获取的固定值。
        private int weight; 
        
        // 当前的权重值,随着RoundRobin过程而改变。
        private AtomicLong current = new AtomicLong(0); 
        
        // 最后更新时间
        private long lastUpdate; 
        public int getWeight() {
            return weight;
        }
        public void setWeight(int weight) {
            this.weight = weight;
            current.set(0);
        }
        
        /* 
        原子方式执行:current = current + weight,
        每次循环时调用。
        */
        public long increaseCurrent() {
            return current.addAndGet(weight);
        }
        
        /*
        原子方式执行:current = current - total,
        对每次循环中current最大的(即选中的)invoker的WeightedRoundRobin调用。
        */
        public void sel(int total) {
            current.addAndGet(-1 * total);
        }
        public long getLastUpdate() {
            return lastUpdate;
        }
        public void setLastUpdate(long lastUpdate) {
            this.lastUpdate = lastUpdate;
        }
    }

2.2 选择过程

确定权重的过程,就是比较和更新WeightedRoundRobin对象的过程。

每次循环,对于WeightedRoundRobin对象进行以下操作:

  • 1 current = currrent + weight;
  • 2 current值最大的选中的invoker;
  • 3 累加totalWeight:totalWeight += weight[i]。

循环结束后:

  • 4 对于选中的invoker: current = current - totalWeight。

目的: 权重越小的值,需要经过越多的循环,才能成为最大。


    // WeightedRoundRobin的存储map
    private ConcurrentMap<String, ConcurrentMap<String, WeightedRoundRobin>> methodWeightMap = new ConcurrentHashMap<String, ConcurrentMap<String, WeightedRoundRobin>>();

    @Override
    protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {

        // {group}/{interfaceName}:{version}.foo
        String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();

        // dubbo://xxxx   ->  WeightedRoundRobin
        ConcurrentMap<String, WeightedRoundRobin> map = methodWeightMap.get(key);
        if (map == null) {
            methodWeightMap.putIfAbsent(key, new ConcurrentHashMap<String, WeightedRoundRobin>());
            map = methodWeightMap.get(key);
        }
        int totalWeight = 0; // weight综合
        long maxCurrent = Long.MIN_VALUE; // 当前的最大current,默认Long.MIN_VALUE。
        long now = System.currentTimeMillis();
        Invoker<T> selectedInvoker = null; // 选中的Invoker
        WeightedRoundRobin selectedWRR = null; // 选中的Invoker对应的WeightedRoundRobin对象
        for (Invoker<T> invoker : invokers) {

            String identifyString = invoker.getUrl().toIdentityString();
            WeightedRoundRobin weightedRoundRobin = map.get(identifyString);

            // 从当前调用配置中,获取weight。
            int weight = getWeight(invoker, invocation);

            if (weightedRoundRobin == null) {
                weightedRoundRobin = new WeightedRoundRobin();
                weightedRoundRobin.setWeight(weight);
                map.putIfAbsent(identifyString, weightedRoundRobin);
            }
            if (weight != weightedRoundRobin.getWeight()) {
                //weight changed
                // 更新weight
                weightedRoundRobin.setWeight(weight);
            }

            // 1 对当前的WRR执行:current = current + weight,并返回current。
            long cur = weightedRoundRobin.increaseCurrent();

            // 设置最后更新时间
            weightedRoundRobin.setLastUpdate(now);
            if (cur > maxCurrent) {
            // 2 选择current最大的WRR。
                maxCurrent = cur;
                selectedInvoker = invoker;
                selectedWRR = weightedRoundRobin;
            }
            
            // 3 累加weight
            totalWeight += weight;
        }

        // 如果invokers和map数量不相等,则更新map。
        if (!updateLock.get() && invokers.size() != map.size()) {
            if (updateLock.compareAndSet(false, true)) {
                try {
                    // copy -> modify -> update reference
                    ConcurrentMap<String, WeightedRoundRobin> newMap = new ConcurrentHashMap<>(map);
                    newMap.entrySet().removeIf(item -> now - item.getValue().getLastUpdate() > RECYCLE_PERIOD);
                    methodWeightMap.put(key, newMap);
                } finally {
                    updateLock.set(false);
                }
            }
        }
        if (selectedInvoker != null) {
            // 4 对选中的invoker的WRR: current = current - totalWeight
            selectedWRR.sel(totalWeight);
            return selectedInvoker;
        }
        // should not happen here
        return invokers.get(0);
    }

3 RandomLoadBalance

选择过程:

  • 1 将各权重放入数据,下标为invokers的下标;
  • 2 以totalWeight为界限产生随机数;
  • 3 weights从后向前,逐步执行offset -= weights[i],当offset<0时,则为要选择的下标。

例如 weights[] = {10, 20, 30}, 则totalWeight = 60;

如果random后:offset=40,则循环选择过程为:

第1次循环:offset = 40 - 30 = 10;

第2次循环:offset = 10 - 20 = -10 < 0,则选中20。

    @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
        int firstWeight = getWeight(invokers.get(0), invocation);
        weights[0] = firstWeight;
        // The sum of weights
        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) {
            // 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));
    }

4 其它

Dubbo中的LoadBalance还有:

  • LeastActiveLoadBalance : 最小活跃数均衡;
  • ConsistentHashLoadBalance : 一致性哈希均衡。

有兴趣的朋友可以深入研究一下。