美文网首页dubbo
dubbo - 负载均衡策略

dubbo - 负载均衡策略

作者: 晴天哥_王志 | 来源:发表于2019-05-08 23:12 被阅读119次

    开篇

    这篇文章的目的是为了讲解清楚dubbo负载均衡的底层实现机制,根据浏览的网上资料来看似乎随着dubbo的版本更新有一些变化,不过大概分类就是那么多,感兴趣的可以自行阅读。

    负载均衡类依赖图

    LoadBalance.jpg

    RoundRobinLoadBalance

    说明

    • RoundRobinLoadBalance基于权重的轮询

    • 如果所有provider的权重都是一致的,那么就变成轮询的状态,invokers.get(currentSequence % length)。

    • 如果所有provider的权重并不完全一致,那么就计算权重和并通过currentSequence % weightSum生成随机数。通过双层for循环直至mod值为0后返回。内层for负责每次都把所有的provider的权重减一,通过外层for循环控制,直到mod为0返回对应的provider。

    • 假设有服务器集合(服务器名:其权重值):A(S0:1, S1:5, S2:5, S3:1, S4:3),权重值不同,使用基于权重值的轮询算法。计算权重值总和(15),初始化一个原子性整数的序列并构建服务器与其权重值的映射集合,只要遍历一次映射集合。若当前所在的序列值为20,与15取模得5,开始遍历映射集合, mod = 5。
      第一轮过后,服务器集合改变为A(S0:0, S1:4, S2:4, S3:0, S4:2),mod=0,
      第二轮过后,S1符合mod=0且S1的value=4>0的条件,所以会选择 S1 服务器。

    public class RoundRobinLoadBalance extends AbstractLoadBalance {
    
        public static final String NAME = "roundrobin";
    
        private final ConcurrentMap<String, AtomicPositiveInteger> sequences = 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(); // Number of invokers
            int maxWeight = 0; // The maximum weight
            int minWeight = Integer.MAX_VALUE; // The minimum weight
            final LinkedHashMap<Invoker<T>, IntegerWrapper> invokerToWeightMap = new LinkedHashMap<Invoker<T>, IntegerWrapper>();
            int weightSum = 0;
            // 遍历invoker,记录权重最大值和最小值;如果权重值大于0,将invoker及其权重值包装类放入map集合,并累加权重值。
            for (int i = 0; i < length; i++) {
                int weight = getWeight(invokers.get(i), invocation);
                maxWeight = Math.max(maxWeight, weight); // Choose the maximum weight
                minWeight = Math.min(minWeight, weight); // Choose the minimum weight
                if (weight > 0) {
                    invokerToWeightMap.put(invokers.get(i), new IntegerWrapper(weight));
                    weightSum += weight;
                }
            }
    
            // 每个key维持一个sequence对象
            AtomicPositiveInteger sequence = sequences.get(key);
            if (sequence == null) {
                sequences.putIfAbsent(key, new AtomicPositiveInteger());
                sequence = sequences.get(key);
            }
    
            // 基于每个sequence自增然后求余然后进行选择
            int currentSequence = sequence.getAndIncrement();
            if (maxWeight > 0 && minWeight < maxWeight) {
                int mod = currentSequence % weightSum;
                for (int i = 0; i < maxWeight; i++) {
                    for (Map.Entry<Invoker<T>, IntegerWrapper> each : invokerToWeightMap.entrySet()) {
                        final Invoker<T> k = each.getKey();
                        final IntegerWrapper v = each.getValue();
                        if (mod == 0 && v.getValue() > 0) {
                            return k;
                        }
                        if (v.getValue() > 0) {
                            v.decrement();
                            mod--;
                        }
                    }
                }
            }
            // 权重一致就蜕化为轮询操作
            return invokers.get(currentSequence % length);
        }
    }
    

    RandomLoadBalance

    • RandomLoadBalance的实现逻辑分为权重相同和权重不同的情况。
    • 权重相同的情况下通过random.nextInt(provider的个数)随机选取一个。
    • 权重不同的情况下通过累加权重得到totalWeight,然后random.nextInt(totalWeight)获取随机数offset,基于offset开始遍历所有provider的权重找到第一个让offset的值为0的provider。
    • 这种策略大概率能够让权重大的provider获取更多机会同时兼顾了权重小的接口。
    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(); // Number of invokers
            int totalWeight = 0; // The sum of weights
            boolean sameWeight = true; // Every invoker has the same weight?
            for (int i = 0; i < length; i++) {
                int weight = getWeight(invokers.get(i), invocation);
                totalWeight += weight; // Sum
                if (sameWeight && i > 0
                        && weight != getWeight(invokers.get(i - 1), invocation)) {
                    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 = random.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(random.nextInt(length));
        }
    }
    

    LeastActiveLoadBalance

    • LeastActiveLoadBalance就是选择活跃数最少的provider,活跃数指代的就是调用次数-响应次数,活跃数越少说明响应越快。
    • LeastActiveLoadBalance的配置某种意义上来说选择性能最优的机器来提供更快的服务。
    • LeastActiveLoadBalance的选择leastActive的逻辑很简单,每次重启或者遇到更小的Active数的时候重新开始计数,然后基于重新计数的基础之上统计相同的leastActive进行选择。
    • 如果leastActive只有一个就直接返回,如果有多个那么按照权重方式进行返回,权重的方式就是计算总权重然后基于总权重生成随机数,通过遍历leastActive的provider找除第一个让随机数为0的provider。
    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(); // Number of invokers
            int leastActive = -1; // The least active value of all invokers
            int leastCount = 0; // The number of invokers having the same least active value (leastActive)
            int[] leastIndexs = new int[length]; // The index of invokers having the same least active value (leastActive)
            int totalWeight = 0; // The sum of weights
            int firstWeight = 0; // Initial value, used for comparision
            boolean sameWeight = true; // Every invoker has the same weight value?
            for (int i = 0; i < length; i++) {
                Invoker<T> invoker = invokers.get(i);
                int active = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName()).getActive(); // Active number
                int weight = invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.WEIGHT_KEY, Constants.DEFAULT_WEIGHT); // Weight
                if (leastActive == -1 || active < leastActive) { // Restart, when find a invoker having smaller least active value.
                    leastActive = active; // Record the current least active value
                    leastCount = 1; // Reset leastCount, count again based on current leastCount
                    leastIndexs[0] = i; // Reset
                    totalWeight = weight; // Reset
                    firstWeight = weight; // Record the weight the first invoker
                    sameWeight = true; // Reset, every invoker has the same weight value?
                } else if (active == leastActive) { // If current invoker's active value equals with leaseActive, then accumulating.
                    leastIndexs[leastCount++] = i; // Record index number of this invoker
                    totalWeight += weight; // Add this invoker's weight to totalWeight.
                    // If every invoker has the same weight?
                    if (sameWeight && i > 0
                            && weight != firstWeight) {
                        sameWeight = false;
                    }
                }
            }
            // assert(leastCount > 0)
            if (leastCount == 1) {
                // If we got exactly one invoker having the least active value, return this invoker directly.
                return invokers.get(leastIndexs[0]);
            }
            if (!sameWeight && totalWeight > 0) {
                // If (not every invoker has the same weight & at least one invoker's weight>0), select randomly based on totalWeight.
                int offsetWeight = random.nextInt(totalWeight);
                // Return a invoker based on the random value.
                for (int i = 0; i < leastCount; i++) {
                    int leastIndex = leastIndexs[i];
                    offsetWeight -= getWeight(invokers.get(leastIndex), invocation);
                    if (offsetWeight <= 0)
                        return invokers.get(leastIndex);
                }
            }
            return invokers.get(leastIndexs[random.nextInt(leastCount)]);
        }
    }
    

    最小活跃数统计

    • 在ActiveLimitFilter的拦截器当中,每次调用前RpcStatus.beginCount()增加活跃数,在返回结果后RpcStatus.endCount()减少活跃数。
    • 如果系统响应慢,那么对应的活跃数就会多,活跃数说明系统比较慢,那么就会少分配请求保持系统响应。
    public class ActiveLimitFilter implements Filter {
    
        public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
            URL url = invoker.getUrl();
            String methodName = invocation.getMethodName();
            int max = invoker.getUrl().getMethodParameter(methodName, Constants.ACTIVES_KEY, 0);
            RpcStatus count = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName());
            // 省略相关代码
            try {
                long begin = System.currentTimeMillis();
                // 增加计数也就是增加活跃数
                RpcStatus.beginCount(url, methodName);
                try {
                    Result result = invoker.invoke(invocation);
                    // 减少计数也就是减少活跃数
                    RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, true);
                    return result;
                } catch (RuntimeException t) {
                    RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, false);
                    throw t;
                }
            } finally {
                if (max > 0) {
                    synchronized (count) {
                        count.notify();
                    }
                }
            }
        }
    }
    
    
    
    
    public class RpcStatus {
    
        public static void beginCount(URL url, String methodName) {
            beginCount(getStatus(url));
            beginCount(getStatus(url, methodName));
        }
    
        private static void beginCount(RpcStatus status) {
            status.active.incrementAndGet();
        }
    
        public static void endCount(URL url, String methodName, long elapsed, boolean succeeded) {
            endCount(getStatus(url), elapsed, succeeded);
            endCount(getStatus(url, methodName), elapsed, succeeded);
        }
    
        private static void endCount(RpcStatus status, long elapsed, boolean succeeded) {
            status.active.decrementAndGet();
            status.total.incrementAndGet();
            status.totalElapsed.addAndGet(elapsed);
            if (status.maxElapsed.get() < elapsed) {
                status.maxElapsed.set(elapsed);
            }
            if (succeeded) {
                if (status.succeededMaxElapsed.get() < elapsed) {
                    status.succeededMaxElapsed.set(elapsed);
                }
            } else {
                status.failed.incrementAndGet();
                status.failedElapsed.addAndGet(elapsed);
                if (status.failedMaxElapsed.get() < elapsed) {
                    status.failedMaxElapsed.set(elapsed);
                }
            }
        }
    }
    

    ConsistentHashLoadBalance

    说明:

    • 一致性hash算法核心的关键点有两个:一致性hashMap的构建,一致性hashMap的select操作。

    • 构建过程:一致性hash的虚拟节点采用了TreeMap对象,针对每个provider根据url的address字段虚拟节点个数/4的偏移量生成128的digest字段,针对128位的digest字段(16个字节)按照每4个字节进行进行计算生成4个虚拟节点放到TreeMap当中。假设有160个虚拟节点,160/4=40个待处理虚拟节点,针对40个待处理虚拟节点中的每个节点生成16个字节的digest,16个字节中digest分为4份进行保存,最终依然是160个虚拟节点

    • 查询过程:查询过程根据指定参数进行md5+hash()计算hash值,找到比hash值小的treeMap然后选择treeMap的根节点选择provider,如果找不到比hash小的treeMap就选择treeMap的最小根节点。

    public class ConsistentHashLoadBalance extends AbstractLoadBalance {
    
        private final ConcurrentMap<String, ConsistentHashSelector<?>> selectors = new ConcurrentHashMap<String, ConsistentHashSelector<?>>();
    
        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.identityHashCode != 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;
    
            ConsistentHashSelector(List<Invoker<T>> invokers, String methodName, int identityHashCode) {
                this.virtualInvokers = new TreeMap<Long, Invoker<T>>();
                this.identityHashCode = identityHashCode;
                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) {
                    String address = invoker.getUrl().getAddress();
                    for (int i = 0; i < replicaNumber / 4; i++) {
                        byte[] digest = md5(address + i);
                        for (int h = 0; h < 4; h++) {
                            long m = hash(digest, h);
                            virtualInvokers.put(m, invoker);
                        }
                    }
                }
            }
    
            public Invoker<T> select(Invocation invocation) {
                String key = toKey(invocation.getArguments());
                byte[] digest = md5(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) {
                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) << 8)
                        | (digest[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;
                try {
                    bytes = value.getBytes("UTF-8");
                } catch (UnsupportedEncodingException e) {
                    throw new IllegalStateException(e.getMessage(), e);
                }
                md5.update(bytes);
                return md5.digest();
            }
    
        }
    
    }
    

    相关文章

      网友评论

        本文标题:dubbo - 负载均衡策略

        本文链接:https://www.haomeiwen.com/subject/ngjeoqtx.html