How dubbo load balancing is achieved?

dubbo load balancing is implemented by all subclasses AbstractLoadBalance

RandomLoadBalance random

On a high probability of collision cross-section, but the call volume the greater the more evenly distributed, but also by the weight more evenly probability rights, in favor of the provider dynamically adjust the weights.

  1. Gets the number of invoker
  2. Get the first invoker weight, and copied to firstWeight
  3. Invoker collection cycle, the sum of all the weights thereof, and to copy totalWeight, if the weight is not equal, false is sameWeight
  4. If the right set of heavy invoker not all equal, then get a random number between 1 and totalWeight, assigned to offset property
  5. Invoker loop through the collection, and obtain the weights and offset subtraction, when the offset reduced to less than zero, it returns the inovker
  6. If the weights are equal, then the invoker taken directly set inside a random number in return
    @Override
    protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        int length = invokers.size(); // Number of invokers
        boolean sameWeight = true; // Every invoker has the same weight?
        int firstWeight = getWeight(invokers.get(0), invocation);
        int totalWeight = firstWeight; // The sum of weights
        for (int i = 1; i < length; i++) {
            int weight = getWeight(invokers.get(i), invocation);
            totalWeight += weight; // Sum
            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 -= getWeight(invokers.get(i), invocation);
                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));
    }

RoundRobinLoadBalance poll

Provider exist request slow accumulation of problems, such as: the second machine is very slow, but did not hang, when transferred to a second request on the card in that, over time, all requests are transferred to the card in the second stage.

In the old version, Dubbo will determine a maximum weight and a minimum weight, if the weights are equal, then press modulo, each taking after the value plus one; If the weights are not equal, the order of redistribution according to the weights.

In the new version, this class has been restructured.

  1. Obtaining a map according to the ServiceKey MethodName example inside manner methodWeightMap + from this example, for the first time. If not, the method proceeds to, placed in the instance of a methodWeightMap in Examples and the acquired map named
  2. Through all invokers
  3. To get the current identifyString invoker as a key, to obtain the map weightedRoundRobin instance, if there are no add a map
  4. If weightedRoundRobin weight and current weight invoker of different weight, indicating that the weight has changed, you need to re-set
  5. Get current weightedRoundRobin invoker example corresponding the current, plus the current weight and the weight of the invoker
  6. Set last update time weightedRoundRobin
  7. 0 maxCurrent beginning is provided, if the current is greater than the current value of weightedRoundRobin the assignment maxCurrent
  8. After traversing the greatest weight will be right and the invoker invoker of selectedInvoker corresponding weightedRoundRobin assigned to the selectedWRR, also have the right to re-sum totalWeight
  9. Then selectedWRR in the current property minus totalWeight, and return selectedInvoker

This view is obviously not clear enough, let's give an example:

假定有3台dubbo provider:

10.0.0.1:20884, weight=2
10.0.0.1:20886, weight=3
10.0.0.1:20888, weight=4

totalWeight=9;

那么第一次调用的时候:
10.0.0.1:20884, weight=2    selectedWRR -> current = 2
10.0.0.1:20886, weight=3    selectedWRR -> current = 3
10.0.0.1:20888, weight=4    selectedWRR -> current = 4
 
selectedInvoker-> 10.0.0.1:20888 
调用 selectedWRR.sel(totalWeight); 
10.0.0.1:20888, weight=4    selectedWRR -> current = -5
返回10.0.0.1:20888这个实例

那么第二次调用的时候:
10.0.0.1:20884, weight=2    selectedWRR -> current = 4
10.0.0.1:20886, weight=3    selectedWRR -> current = 6
10.0.0.1:20888, weight=4    selectedWRR -> current = -1

selectedInvoker-> 10.0.0.1:20886 
调用 selectedWRR.sel(totalWeight); 
10.0.0.1:20886 , weight=4   selectedWRR -> current = -3
返回10.0.0.1:20886这个实例

那么第三次调用的时候:
10.0.0.1:20884, weight=2    selectedWRR -> current = 6
10.0.0.1:20886, weight=3    selectedWRR -> current = 0
10.0.0.1:20888, weight=4    selectedWRR -> current = 3

selectedInvoker-> 10.0.0.1:20884
调用 selectedWRR.sel(totalWeight); 
10.0.0.1:20884, weight=2    selectedWRR -> current = -3
返回10.0.0.1:20884这个实例
    protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();
        ConcurrentMap<String, WeightedRoundRobin> map = methodWeightMap.get(key);
        if (map == null) {
            methodWeightMap.putIfAbsent(key, new ConcurrentHashMap<String, WeightedRoundRobin>());
            map = methodWeightMap.get(key);
        }
        int totalWeight = 0;
        long maxCurrent = Long.MIN_VALUE;
        long now = System.currentTimeMillis();
        Invoker<T> selectedInvoker = null;
        WeightedRoundRobin selectedWRR = null;
        for (Invoker<T> invoker : invokers) {
            String identifyString = invoker.getUrl().toIdentityString();
            WeightedRoundRobin weightedRoundRobin = map.get(identifyString);
            int weight = getWeight(invoker, invocation);
            if (weight < 0) {
                weight = 0;
            }
            if (weightedRoundRobin == null) {
                weightedRoundRobin = new WeightedRoundRobin();
                weightedRoundRobin.setWeight(weight);
                map.putIfAbsent(identifyString, weightedRoundRobin);
                weightedRoundRobin = map.get(identifyString);
            }
            if (weight != weightedRoundRobin.getWeight()) {
                //weight changed
                weightedRoundRobin.setWeight(weight);
            }
            long cur = weightedRoundRobin.increaseCurrent();
            weightedRoundRobin.setLastUpdate(now);
            if (cur > maxCurrent) {
                maxCurrent = cur;
                selectedInvoker = invoker;
                selectedWRR = weightedRoundRobin;
            }
            totalWeight += weight;
        }
        if (!updateLock.get() && invokers.size() != map.size()) {
            if (updateLock.compareAndSet(false, true)) {
                try {
                    // copy -> modify -> update reference
                    ConcurrentMap<String, WeightedRoundRobin> newMap = new ConcurrentHashMap<String, WeightedRoundRobin>();
                    newMap.putAll(map);
                    Iterator<Entry<String, WeightedRoundRobin>> it = newMap.entrySet().iterator();
                    while (it.hasNext()) {
                        Entry<String, WeightedRoundRobin> item = it.next();
                        if (now - item.getValue().getLastUpdate() > RECYCLE_PERIOD) {
                            it.remove();
                        }
                    }
                    methodWeightMap.put(key, newMap);
                } finally {
                    updateLock.set(false);
                }
            }
        }
        if (selectedInvoker != null) {
            selectedWRR.sel(totalWeight);
            return selectedInvoker;
        }
        // should not happen here
        return invokers.get(0);
    }

LeastActiveLoadBalance minimum number of active calls

So that less slow provider receives a request, because the greater the difference between before and after the call will be slower provider counts.

  1. Through all the invoker
  2. Gets the current number of active invoker, the call is RpcStatus the getStatus method, active filter which will record the number of each method
  3. Get the current invoker weight
  4. The first time you come in or the current number of active invoker is smaller than the minimum number of active
  5. So the leastActive set to the current number of active invoker, set 1, a position leastIndexes array is set to 1 leastCount, record what totalWeight and firstWeight
  6. If the condition of point 4, it is judged that the number of currently active and the minimum number of active invoker are equal
  7. If the meet point 6, then the current weight is added to the totalWeight in and put leastIndexes array record at the same minimum number of active index; let's look at whether all of the same weight
  8. If the invoker invoker only one set of active minimal, less direct return
  9. If the weights are not equal, the random weight determining which of the heavy weight range of Invoker
  10. Equally weighted, can directly randomly selected Invoker

最小活跃数算法实现:
假定有3台dubbo provider:

10.0.0.1:20884, weight=2,active=2
10.0.0.1:20886, weight=3,active=4
10.0.0.1:20888, weight=4,active=3
active=2最小,且只有一个2,所以选择10.0.0.1:20884

假定有3台dubbo provider:

10.0.0.1:20884, weight=2,active=2
10.0.0.1:20886, weight=3,active=2
10.0.0.1:20888, weight=4,active=3
active=2最小,且有2个,所以从[10.0.0.1:20884,10.0.0.1:20886 ]中选择;
接下来的算法与随机算法类似:

假设offset=1(即random.nextInt(5)=1)
1-2=-1<0?是,所以选中 10.0.0.1:20884, weight=2
假设offset=4(即random.nextInt(5)=4)
4-2=2<0?否,这时候offset=2, 2-3<0?是,所以选中 10.0.0.1:20886, weight=3
 1: public class LeastActiveLoadBalance extends AbstractLoadBalance {
 2: 
 3:     public static final String NAME = "leastactive";
 4: 
 5:     private final Random random = new Random();
 6: 
 7:     @Override
 8:     protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
 9:         int length = invokers.size(); // 总个数
10:         int leastActive = -1; // 最小的活跃数
11:         int leastCount = 0; // 相同最小活跃数的个数
12:         int[] leastIndexes = new int[length]; // 相同最小活跃数的下标
13:         int totalWeight = 0; // 总权重
14:         int firstWeight = 0; // 第一个权重,用于于计算是否相同
15:         boolean sameWeight = true; // 是否所有权重相同
16:         // 计算获得相同最小活跃数的数组和个数
17:         for (int i = 0; i < length; i++) {
18:             Invoker<T> invoker = invokers.get(i);
19:             int active = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName()).getActive(); // 活跃数
20:             int weight = invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.WEIGHT_KEY, Constants.DEFAULT_WEIGHT); // 权重
21:             if (leastActive == -1 || active < leastActive) { // 发现更小的活跃数,重新开始
22:                 leastActive = active; // 记录最小活跃数
23:                 leastCount = 1; // 重新统计相同最小活跃数的个数
24:                 leastIndexes[0] = i; // 重新记录最小活跃数下标
25:                 totalWeight = weight; // 重新累计总权重
26:                 firstWeight = weight; // 记录第一个权重
27:                 sameWeight = true; // 还原权重相同标识
28:             } else if (active == leastActive) { // 累计相同最小的活跃数
29:                 leastIndexes[leastCount++] = i; // 累计相同最小活跃数下标
30:                 totalWeight += weight; // 累计总权重
31:                 // 判断所有权重是否一样
32:                 if (sameWeight && weight != firstWeight) {
33:                     sameWeight = false;
34:                 }
35:             }
36:         }
37:         // assert(leastCount > 0)
38:         if (leastCount == 1) {
39:             // 如果只有一个最小则直接返回
40:             return invokers.get(leastIndexes[0]);
41:         }
42:         if (!sameWeight && totalWeight > 0) {
43:             // 如果权重不相同且权重大于0则按总权重数随机
44:             int offsetWeight = random.nextInt(totalWeight);
45:             // 并确定随机值落在哪个片断上
46:             for (int i = 0; i < leastCount; i++) {
47:                 int leastIndex = leastIndexes[i];
48:                 offsetWeight -= getWeight(invokers.get(leastIndex), invocation);
49:                 if (offsetWeight <= 0) {
50:                     return invokers.get(leastIndex);
51:                 }
52:             }
53:         }
54:         // 如果权重相同或权重为0则均等随机
55:         return invokers.get(leastIndexes[random.nextInt(leastCount)]);
56:     }
57: 
58: }

ConsistentHashLoadBalance consistency Hash

Request is always sent to the same parameters of the same provider. When a station linked to the provider, the original request sent to the provider, based on the virtual node, in equal shares to other providers, it will not cause dramatic changes.

  1. Based invokers set, the memory address of the object is calculated based on the hash value defined
  2. Get ConsistentHashSelector object. If it is empty, or change the definition of a hash value (Description invokers set of changes), created a new ConsistentHashSelector objects
  3. Call ConsistentHashSelector object's select method
 1: public class ConsistentHashLoadBalance extends AbstractLoadBalance {
 2: 
 3:     /**
 4:      * 服务方法与一致性哈希选择器的映射
 5:      *
 6:      * KEY:serviceKey + "." + methodName
 7:      */
 8:     private final ConcurrentMap<String, ConsistentHashSelector<?>> selectors = new ConcurrentHashMap<String, ConsistentHashSelector<?>>();
 9: 
10:     @SuppressWarnings("unchecked")
11:     @Override
12:     protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
13:         String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();
14:         // 基于 invokers 集合,根据对象内存地址来计算定义哈希值
15:         int identityHashCode = System.identityHashCode(invokers);
16:         // 获得 ConsistentHashSelector 对象。若为空,或者定义哈希值变更(说明 invokers 集合发生变化),进行创建新的 ConsistentHashSelector 对象
17:         ConsistentHashSelector<T> selector = (ConsistentHashSelector<T>) selectors.get(key);
18:         if (selector == null || selector.identityHashCode != identityHashCode) {
19:             selectors.put(key, new ConsistentHashSelector<T>(invokers, invocation.getMethodName(), identityHashCode));
20:             selector = (ConsistentHashSelector<T>) selectors.get(key);
21:         }
22:         return selector.select(invocation);
23:     }
24: }

ConsistentHashSelector consistent hashing selector

ConsistentHashSelector, is ConsistentHashLoadBalance inner class, consistent hashing selector, based Ketama algorithm.

/**
 * 虚拟节点与 Invoker 的映射关系
 */
private final TreeMap<Long, Invoker<T>> virtualInvokers;
/**
 * 每个Invoker 对应的虚拟节点数
 */
private final int replicaNumber;
/**
 * 定义哈希值
 */
private final int identityHashCode;
/**
 * 取值参数位置数组
 */
private final int[] argumentIndex;

  1: ConsistentHashSelector(List<Invoker<T>> invokers, String methodName, int identityHashCode) {
  2:     this.virtualInvokers = new TreeMap<Long, Invoker<T>>();
  3:     // 设置 identityHashCode
  4:     this.identityHashCode = identityHashCode;
  5:     URL url = invokers.get(0).getUrl();
  6:     // 初始化 replicaNumber
  7:     this.replicaNumber = url.getMethodParameter(methodName, "hash.nodes", 160);
  8:     // 初始化 argumentIndex
  9:     String[] index = Constants.COMMA_SPLIT_PATTERN.split(url.getMethodParameter(methodName, "hash.arguments", "0"));
 10:     argumentIndex = new int[index.length];
 11:     for (int i = 0; i < index.length; i++) {
 12:         argumentIndex[i] = Integer.parseInt(index[i]);
 13:     }
 14:     // 初始化 virtualInvokers
 15:     for (Invoker<T> invoker : invokers) {
 16:         String address = invoker.getUrl().getAddress();
 17:         // 每四个虚拟结点为一组,为什么这样?下面会说到
 18:         for (int i = 0; i < replicaNumber / 4; i++) {
 19:             // 这组虚拟结点得到惟一名称
 20:             byte[] digest = md5(address + i);
 21:             // Md5是一个16字节长度的数组,将16字节的数组每四个字节一组,分别对应一个虚拟结点,这就是为什么上面把虚拟结点四个划分一组的原因
 22:             for (int h = 0; h < 4; h++) {
 23:                 // 对于每四个字节,组成一个long值数值,做为这个虚拟节点的在环中的惟一key
 24:                 long m = hash(digest, h);
 25:                 virtualInvokers.put(m, invoker);
 26:             }
 27:         }
 28:     }
 29: }
public Invoker<T> select(Invocation invocation) {
    // 基于方法参数,获得 KEY
    String key = toKey(invocation.getArguments());
    // 计算 MD5 值
    byte[] digest = md5(key);
    // 计算 KEY 值
    return selectForKey(hash(digest, 0));
}

private String toKey(Object[] args) {
    StringBuilder buf = new StringBuilder();
    for (int i : argumentIndex) {
        if (i >= 0 && i < args.length) {
            buf.append(args[i]);
        }
    }
    return buf.toString();
}

private Invoker<T> selectForKey(long hash) {
    // 得到大于当前 key 的那个子 Map ,然后从中取出第一个 key ,就是大于且离它最近的那个 key
    Map.Entry<Long, Invoker<T>> entry = virtualInvokers.tailMap(hash, true).firstEntry();
    // 不存在,则取 virtualInvokers 第一个
    if (entry == null) {
        entry = virtualInvokers.firstEntry();
    }
    // 存在,则返回
    return entry.getValue();
}

Guess you like

Origin www.cnblogs.com/luozhiyun/p/10963116.html