Dubbo's load balancing strategy algorithm



Dubbo provides four kinds of balancing strategies in cluster load balancing, the default is RandomLoadBalance

/**
* Random LoadBalance
* Random, set the random probability according to the weight
* The probability of collision on a section is high, but the larger the number of calls, the more uniform the distribution , and the weights are relatively uniform after using the weights according to the probability, which is conducive to dynamically adjusting the provider weights.
* @author junlee 2016-08-07
* @Email: [email protected]
* @version
*/
public class RandomLoadBalance extends AbstractLoadBalance {
    public static final String NAME = "random";
    private final Random random = new Random();
    protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        int length = invokers.size(); // total number
        int totalWeight = 0; // total weight
        boolean sameWeight = true; // whether the weights are all the same
        for (int i = 0; i < length; i++) {
            int weight = getWeight(invokers.get(i), invocation);
            totalWeight += weight; // cumulative total weight
            if (sameWeight && i > 0
                    && weight != getWeight(invokers.get(i - 1), invocation)) {
                sameWeight = false; // Calculate whether all weights are the same
            }
        }
        if (totalWeight > 0 && ! sameWeight) {
            // If the weights are not the same and the weight is greater than 0, press total weights random
            int offset = random.nextInt(totalWeight);
            // and determine which segment the random value falls on
            for (int i = 0; i < length; i++) {
                offset -= getWeight(invokers.get(i) , invocation);
                if (offset < 0) {
                    return invokers.get(i);
                }
            }
        }
        // equally random if the weights are the same or 0
        return invokers.get(random.nextInt(length));
    }
}
/**
* RoundRobin LoadBalance
* Round robin, set the round robin ratio according to the weight after the convention.
* There is a problem with slow providers accumulating requests. The second machine is slow, but it doesn't hang. When the request is transferred to the second machine, it is stuck there. All requests are stuck on the second machine.
* @author junlee 2016- 08-07
* @Email: [email protected]
* @version
*/
public class RoundRobinLoadBalance extends AbstractLoadBalance {
    public static final String NAME = "roundrobin";
        private final ConcurrentMap<String, AtomicPositiveInteger> sequences = new ConcurrentHashMap<String, AtomicPositiveInteger>();
    private final ConcurrentMap<String, AtomicPositiveInteger> weightSequences = new ConcurrentHashMap<String, AtomicPositiveInteger>();
    protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();
        int length = invokers.size(); // 总个数
        int maxWeight = 0; // 最大权重
        int minWeight = Integer.MAX_VALUE; // 最小权重
        for (int i = 0; i < length; i++) {
            int weight = getWeight(invokers.get(i),invocation);
            maxWeight = Math.max(maxWeight, weight); // 累计最大权重
            minWeight = Math.min(minWeight, weight); // 累计最小权重
        }
        if (maxWeight > 0 && minWeight < maxWeight) { // 权重不一样
            AtomicPositiveInteger weightSequence = weightSequences.get(key);
            if (weightSequence == null) {
                weightSequences.putIfAbsent(key, new AtomicPositiveInteger());
                weightSequence = weightSequences.get(key);
            }
            int currentWeight = weightSequence.getAndIncrement() % maxWeight;
            List<Invoker<T>> weightInvokers = new ArrayList<Invoker<T>>();
            for (Invoker<T> invoker : invokers) { // 筛选权重大于当前权重基数的Invoker
                if (getWeight(invoker, invocation) > currentWeight) {
                    weightInvokers.add(invoker);
                }
            }
            int weightLength = weightInvokers.size();
            if (weightLength == 1) {
                return weightInvokers.get(0);
            } else if (weightLength > 1) {
                invokers = weightInvokers;
                length = invokers.size();
            }
        }
        AtomicPositiveInteger sequence = sequences.get(key);
        if (sequence == null) {
            sequences.putIfAbsent(key, new AtomicPositiveInteger());
            sequence = sequences.get(key);
        }
        // modulo round robin
        return invokers.get(sequence.getAndIncrement() % length);
    }
}
/**
* LeastActive LoadBalance
* Minimum number of active calls, random for the same active number, active number refers to the difference between the counts before and after the call.
* Make the slow provider receive fewer requests, because the slower the provider's count difference before and after the call will be larger.
* @author junlee 2016-08-07
* @Email: [email protected]
* @version
*/
public class LeastActiveLoadBalance extends AbstractLoadBalance {
    public static final String NAME = "leastactive";
        private final Random random = new Random();
    protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        int length = invokers.size(); // total number
        int leastActive = -1; // smallest Active count
        int leastCount = 0; // Number of the same minimum active count
        int[] leastIndexs = new int[length]; // Subscript of the same minimum active count
        int totalWeight = 0; // Total weight
        int firstWeight = 0; // The first weight is used to calculate whether it is the same
        boolean sameWeight = true; // whether all weights are the same
        for (int i = 0; i < length; i++) {
        Invoker<T> invoker = invokers.get(i) ;
            int active = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName()).getActive(); // active count
            int weight = invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.WEIGHT_KEY, Constants.DEFAULT_WEIGHT); // weight
            if (leastActive == -1 || active < leastActive) { // find less active count, restart
                leastActive = active; // record the minimum active count
                leastCount = 1; // re-count the same minimum active count
                leastIndexs[0] = i; // re-record the minimum active count subscript
                totalWeight = weight; / / Re-accumulate the total weight
                firstWeight = weight; // Record the first weight
                sameWeight = true; // Restore the same weight identifier
            } else if (active == leastActive) { // Accumulate the same minimum active count
                leastIndexs[leastCount ++] = i; // Accumulate the same minimum active subscript
                totalWeight += weight; // Accumulate total weight
                // Check if all weights are the same
                if (sameWeight && i > 0
                        && weight != firstWeight) {
                    sameWeight = false;
                }
            }
        }
        // assert(leastCount > 0)
        if (leastCount == 1) {
            // if there is only one minimum Then return directly
            return invokers.get(leastIndexs[0]);
        }
        if (! sameWeight && totalWeight > 0) {
            // If the weights are not the same and the weight is greater than 0, random according to the total weight
            int offsetWeight = random.nextInt(totalWeight) ;
            // and determine which segment the random value falls on
            for (int i = 0; i < leastCount; i++) {
                int leastIndex = leastIndexs[i];
                offsetWeight -= getWeight(invokers.get(leastIndex), invocation);
                if (offsetWeight <= 0)
                    return invokers.get(leastIndex);
            }
        }
        // if the weight is the same or the weight is 0 Equal random
        return invokers.get(leastIndexs[random.nextInt(leastCount)]);
    }
}
/**
* ConsistentHash LoadBalance
* Consistent Hash, requests with the same parameters are always sent to the same provider.
* When a provider hangs, the request originally sent to the provider will be spread to other providers based on virtual nodes, and will not cause drastic changes.
* @author junlee 2016-08-07
* @Email: [email protected]
* @version
*/
public class ConsistentHashLoadBalance extends AbstractLoadBalance {
    private final ConcurrentMap<String, ConsistentHashSelector<?>> selectors = new ConcurrentHashMap<String, ConsistentHashSelector<?>>();
    @SuppressWarnings("unchecked")
    @Override
    protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();
        int identityHashCode = System.identityHashCode(invokers);
        ConsistentHashSelector<T> selector = (ConsistentHashSelector<T>) selectors.get(key);
        if (selector == null || selector.getIdentityHashCode() != identityHashCode) {
            selectors.put(key, new ConsistentHashSelector<T>(invokers, invocation.getMethodName(), identityHashCode));
            selector = (ConsistentHashSelector<T>) selectors.get(key);
        }
        return selector.select(invocation);
    }
    private static final class ConsistentHashSelector<T> {
    private final TreeMap<Long, Invoker<T>> virtualInvokers;
    private final int                       replicaNumber;
    private final int                       identityHashCode;
    private final int[]                     argumentIndex;
  public ConsistentHashSelector(List<Invoker<T>> invokers, String methodName, int identityHashCode) {
            this.virtualInvokers = new TreeMap<Long, Invoker<T>>();
            this.identityHashCode = System.identityHashCode(invokers);
            URL url = invokers.get(0).getUrl();
            this.replicaNumber = url.getMethodParameter(methodName, "hash.nodes", 160);
            String[] index = Constants.COMMA_SPLIT_PATTERN.split(url.getMethodParameter(methodName, "hash.arguments", "0"));
            argumentIndex = new int[index.length];
            for (int i = 0; i < index.length; i ++) {
                argumentIndex[i] = Integer.parseInt(index[i]);
            }
            for (Invoker<T> invoker : invokers) {
                for (int i = 0; i <replicaNumber / 4; i++) {
                    byte[] digest = md5(invoker.getUrl().toFullString() + i);
                    for (int h = 0; h < 4; h++) {
                        long m = hash(digest, h);
                        virtualInvokers.put(m, invoker);
                    }
                }
            }
        }
        public int getIdentityHashCode() {
            return identityHashCode;
        }
        public Invoker<T> select(Invocation invocation) {
            String key = toKey(invocation.getArguments());
            byte[] digest = md5(key);
            Invoker<T> invoker = sekectForKey(hash(digest, 0));
            return invoker;
        }
        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> sekectForKey(long hash) {
            Invoker<T> invoker;
            Long key = hash;
            if (!virtualInvokers.containsKey(key)) {
                SortedMap<Long, Invoker<T>> tailMap = virtualInvokers.tailMap(key);
                if (tailMap.isEmpty()) {
                    key = virtualInvokers.firstKey();
                } else {
                    key = tailMap.firstKey();
                }
            }
            invoker = virtualInvokers.get(key);
            return invoker;
        }
        private long hash(byte[] digest, int number) {
            return (((long) (digest[3 + number * 4] & 0xFF) << 24)
                    | ((long) (digest[2 + number * 4] & 0xFF) << 16)
                    | ((long) (digest[1 + number * 4] & 0xFF) <<
                    | (digest[0 + number * 4] & 0xFF))
                    & 0xFFFFFFFFL;
        }
        private byte[] md5(String value) {
            MessageDigest md5;
            try {
                md5 = MessageDigest.getInstance("MD5");
            } catch (NoSuchAlgorithmException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            md5.reset();
            byte[] bytes = null;
            try {
                bytes = value.getBytes("UTF-8");
            } catch (UnsupportedEncodingException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            md5.update(bytes);
            return md5.digest();
        }
    }
}

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326696083&siteId=291194637