dubbo中负载均衡算法

当dubbo中服务的提供者存在多个时,就存在服务的集群,集群中如何分配服务的调用就存在一些算法,选择合适的服务来提供服务。

轮询负载均衡算法RoundRobinLoadBalance

轮询顾名思义就是按照顺序一个一个来提供服务,假设有三个服务1,2,3,首先执行服务1,然后2,然后3,紧接着服务1

  • 直接上代码
// 定义一个类似计数器
private static AtomicInteger atomicIndexDirect = new AtomicInteger(0);    
private List<String> appServerAddress = Arrays.asList("http://localhost:1000/1","http://localhost:1001/1","http://localhost:1001/1")
// 服务提供者集群中轮询调用
            if (appServerAddressList.size() == 1) {
                appServerAddress = appServerAddressList.get(0);
            } else {
                // 计数器加一
                int index = atomicIndexDirect.incrementAndGet();
                // 判断是否越界,越界回到原点
                if (index > appServerAddressList.size() - 1) {
                    index = 0;
                    atomicIndexDirect.set(index);
                }
                appServerAddress = appServerAddressList.get(index);
            }

权重随机算法的 RandomLoadBalance

假设有三台服务器[A,B,C],我们设置权重分别是5,3,2,权重总和为10,对于这三台服务器,可以认为A服务器在[0,5),B服务器在[5,8),C服务器在[8,10],此时提供一个随机数生成器(此处认为该随机数生成器算法足够好,产生的数据足够随机),那么让其生成一个[0,10)的随机数,这个数据将会落在其中一个区段,此时就将请求分发到对应

思路就是给每个服务分配权重,然后获取总权重,取总权重生成一个小于总权重的随机数,然后将随机数以每个服务的权重数比对是否在改服务区间

  • 上代码dubbo源码
package org.apache.dubbo.rpc.cluster.loadbalance;

import org.apache.dubbo.common.URL;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;

import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

/**
 * random load balance.
 */
public class RandomLoadBalance extends AbstractLoadBalance {

    public static final String NAME = "random";

    @Override
    protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        // Dubbo多个节点都会包装成invoker模型的形式
        int length = invokers.size();
        boolean sameWeight = true;
        // 每一个invoker的权重
        int[] weights = new int[length];
		// 尝试获取第一个invoker的权重
        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) {
            // 基于总权重,随机一个数据出来
            int offset = ThreadLocalRandom.current().nextInt(totalWeight);
            // 求随机出来的数据落在哪个区间段
            for (int i = 0; i < length; i++) {
                offset -= weights[i];
                if (offset < 0) {
                    return invokers.get(i);
                }
            }
        }
        //如果权重相等,则随机一个数出来
        return invokers.get(ThreadLocalRandom.current().nextInt(length));
    }
}

权重轮询负载均衡算法WeightRoundRobinLoadBalance

思路:将所有的请求个数都保存到map集合中,key是调用的接口,方法,参数类型,value是调用的次数,根据每个服务的权重获取最大权重和最小权重,和总的权重,以及将每个服务的的请求地址和权重保存到map集合中,通过遍历总的权重,利用接口调用次数对总权重取余(将请求分布在权重中),再遍历保存的地址和权重,如何满足条件则返回,不满足则将调用次数减少

当所有服务的权重一致时候,则变为轮询调用

  • 直接上代码
package com.yu.dubbo.core.cluster.loadbalance;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;

public class WeightRoundRobinLoadBalance extends AbstractLoadBalance {
    public static final String NAME = "weightroundrobin";

    protected static class WeightedRoundRobin {
        private AtomicLong current = new AtomicLong(0);

        private long getAndIncrement() {
            return current.getAndIncrement();
        }
    }

    private ConcurrentMap<String, WeightedRoundRobin> methodWeightMap = new ConcurrentHashMap<>();

    @Override
    protected String doSelect(List<String> providers, String interfaceName, String methodName) {
        String key = interfaceName + "." + methodName;
        int length = providers.size();
        int maxWeight = 0; 
        int minWeight = Integer.MAX_VALUE;
        final LinkedHashMap<String, IntegerWrapper> invokerToWeightMap = new LinkedHashMap<>();
        int weightSum = 0;
        //初始化maxWeight,minWeight,weightSum,invokerToWeightMap
        for (int i = 0; i < length; i++) {
            int weight = getWeight(providers.get(i));
            maxWeight = Math.max(maxWeight, weight); // Choose the maximum weight
            minWeight = Math.min(minWeight, weight); // Choose the minimum weight
            if (weight > 0) {
                invokerToWeightMap.put(providers.get(i), new IntegerWrapper(weight));
                weightSum += weight;
            }
        }
        // 获取自增调用次数
        WeightedRoundRobin weightedRoundRobin = methodWeightMap.get(key);
        if (weightedRoundRobin == null) {
            methodWeightMap.putIfAbsent(key, new WeightedRoundRobin());
            weightedRoundRobin = methodWeightMap.get(key);
        }
        // 当前接口调用总次数
        long currentSequence = weightedRoundRobin.getAndIncrement();
        //当权重不一样的时候,通过加权轮询获取到invoker,权值越大,则被选中的几率也越大
        if (maxWeight > 0 && minWeight < maxWeight) {
            long mod = currentSequence % weightSum;
            for (int i = 0; i < maxWeight; i++) {
                //遍历invoker的数量
                for (Map.Entry<String, IntegerWrapper> each : invokerToWeightMap.entrySet()) {
                    final String k = each.getKey();
                    //invoker的权重
                    final IntegerWrapper v = each.getValue();
                    if (mod == 0 && v.getValue() > 0) {
                        return k;
                    }
                    if (v.getValue() > 0) {
                        //当前invoker的可调用次数减1
                        v.decrement();
                        mod--;
                    }
                }
            }
        }
        // Round robin 权重一样的情况下,就取余的方式获取到invoker
        return providers.get((int) (currentSequence % length));
    }

    private static final class IntegerWrapper {
        private int value;

        public IntegerWrapper(int value) {
            this.value = value;
        }

        public int getValue() {
            return value;
        }

        public void setValue(int value) {
            this.value = value;
        }

        public void decrement() {
            this.value--;
        }
    }
}

一致性哈希算法

使用hash算法的同时,新增服务节点需要对所有的服务节点重新映射关系,影响比较大,所有就由了一致性hash算法,增加服务节点时只会小范围影响来重新建立映射关系,还可以通过添加虚拟节点,使得请求尽量分散一些

最小活跃数算法

最小活跃数就是取响应时间最快的服务,哪个响应请求快,就由哪个服务器处理