一、dubbo负载均衡

1、负载均衡设置
1)服务端服务级别
 <dubbo:service interface="..." loadbalance="roundrobin" />
2)客户端服务级别
 <dubbo:reference interface="..." loadbalance="roundrobin" />
3)服务端方法级别
<dubbo:service interface="...">
    <dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:service>
4)客户端方法级别
<dubbo:reference interface="...">
    <dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:reference>
2、RandomLoadBalance
/**
 * 假定有3台dubbo provider:
 *
 *
 * 10.0.0.1:20884, weight=二
 * 10.0.0.1:20886, weight=3
 * 10.0.0.1:20888, weight=4
 * 随机算法的实现:
 * totalWeight=9;
 *
 * 假设offset=1(即random.nextInt(9)=1)
 * 1-2=-1<0?是,所以选中 10.0.0.1:20884, weight=2
 * 假设offset=4(即random.nextInt(9)=4)
 * 4-2=2<0?否,这时候offset=2,2-3<0?是,所以选中 10.0.0.1:20886, weight=3
 * 假设offset=7(即random.nextInt(9)=7)
 * 7-2=5<0?否,这时候offset=5, 5-3=2<0?否,这时候offset=2,
 * 2-4<0?是,所以选中 10.0.0.1:20888, weight=4
 */
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
    int length = invokers.size(); //计算invoker总数量
    int totalWeight = 0; // 计算总权重值
    boolean sameWeight = true; // 判断是否每个invoker的权重都相同,如果相同,就随机选择了
    for (int i = 0; i < length; i++) {
        int weight = getWeight(invokers.get(i), invocation);
        //计算总权重 totalWeight,
        totalWeight += weight; // Sum
        // 检测当前服务提供者的权重与上一个服务提供者的权重是否相同,
        // 不相同的话,则将 sameWeight 置为 false。
        if (sameWeight && i > 0
                && weight != getWeight(invokers.get(i - 1), invocation)) {
            sameWeight = false;
        }
    }
    // 权重不相等,随机后,判断在哪个 Invoker 的权重区间中
    if (totalWeight > 0 && !sameWeight) {
        // If (not every invoker has the same weight & at least one invoker's weight>0), select randomly based on totalWeight.
        // 随机获取一个 [0, totalWeight) 区间内的数字
        int offset = random.nextInt(totalWeight);
        // Return a invoker based on the random value.
        // 循环让 offset 数减去服务提供者权重值,当 offset 小于0时,返回相应的 Invoker。
        // 举例说明一下,我们有 servers = [A, B, C],weights = [5, 3, 2],offset = 7。
        // 第一次循环,offset - 5 = 2 > 0,即 offset > 5,
        // 表明其不会落在服务器 A 对应的区间上。
        // 第二次循环,offset - 3 = -1 < 0,即 5 < offset < 8,
        // 表明其会落在服务器 B 对应的区间上
        for (int i = 0; i < length; i++) {
            // 让随机值 offset 减去权重值
            offset -= getWeight(invokers.get(i), invocation);
            if (offset < 0) {
                // 返回相应的 Invoker
                return invokers.get(i);
            }
        }
    }
    // If all invokers have the same weight value or totalWeight=0, return evenly.
    // 权重相等,平均随机
    return invokers.get(random.nextInt(length));
}
3、RoundRobinLoadBalance

RoundRobinLoadBalance是加权轮训负载均衡,根据加权轮训

/**
 * 假设我们有三台服务器 servers = [A, B, C],对应的权重为 weights = [2, 5, 1]。
 * 接下来对上面的逻辑进行简单的模拟。
 *
 * mod = 0:满足条件,此时直接返回服务器 A
 *
 * mod = 1:需要进行一次递减操作才能满足条件,此时返回服务器 B
 *
 * mod = 2:需要进行两次递减操作才能满足条件,此时返回服务器 C
 *
 * mod = 3:需要进行三次递减操作才能满足条件,经过递减后,服务器权重为 [1, 4, 0],此时返回服务器 A
 *
 * mod = 4:需要进行四次递减操作才能满足条件,经过递减后,服务器权重为 [0, 4, 0],此时返回服务器 B
 *
 * mod = 5:需要进行五次递减操作才能满足条件,经过递减后,服务器权重为 [0, 3, 0],此时返回服务器 B
 *
 * mod = 6:需要进行六次递减操作才能满足条件,经过递减后,服务器权重为 [0, 2, 0],此时返回服务器 B
 *
 * mod = 7:需要进行七次递减操作才能满足条件,经过递减后,服务器权重为 [0, 1, 0],此时返回服务器 B
 *
 * 经过8次调用后,我们得到的负载均衡结果为 [A, B, C, A, B, B, B, B],
 * 次数比 A:B:C = 2:5:1,等于权重比。当 sequence = 8 时,mod = 0,此时重头再来。
 * 从上面的模拟过程可以看出,当 mod >= 3 后,服务器 C 就不会被选中了,因为它的权重被减为0了。
 * 当 mod >= 4 后,服务器 A 的权重被减为0,此后 A 就不会再被选中。
 */
 protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
    // key = 全限定类名 + "." + 方法名,比如 com.xxx.DemoService.sayHello
    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;        // 权重总和
    // 查找最大和最小权重,计算权重总和
    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) {
            // 将 weight 封装到 IntegerWrapper 中
            invokerToWeightMap.put(invokers.get(i), new IntegerWrapper(weight));
            // 累加权重
            weightSum += weight;
        }
    }
    // 查找 key 对应的对应 AtomicPositiveInteger 实例,为空则创建。
    // AtomicPositiveInteger 用于记录服务的调用编号即可
    AtomicPositiveInteger sequence = sequences.get(key);
    if (sequence == null) {
        sequences.putIfAbsent(key, new AtomicPositiveInteger());
        sequence = sequences.get(key);
    }
    // 获得当前调用编号,并递增 + 1,sequence的值是从0开始,而且 getAndIncrement是先得到0, 再增加
    int currentSequence = sequence.getAndIncrement();
    // 如果最小权重小于最大权重,表明服务提供者之间的权重是不相等的
    if (maxWeight > 0 && minWeight < maxWeight) {
        // 使用调用编号对权重总和进行取余操作
        int mod = currentSequence % weightSum;
        for (int i = 0; i < maxWeight; i++) {
            // 遍历 invokerToWeightMap
            for (Map.Entry<Invoker<T>, IntegerWrapper> each : invokerToWeightMap.entrySet()) {
                //获取invoker
                final Invoker<T> k = each.getKey();
                // 获取权重包装类 IntegerWrapper
                final IntegerWrapper v = each.getValue();
                // 如果 mod = 0,且权重大于0,此时返回相应的 Invoker
                if (mod == 0 && v.getValue() > 0) {
                    return k;
                }
                // mod != 0,且权重大于0,此时对权重和 mod 分别进行自减操作
                if (v.getValue() > 0) {
                    v.decrement();
                    mod--;
                }
            }
        }
    }
    // Round robin
    // 服务提供者之间的权重相等,此时通过轮询选择 Invoker
    return invokers.get(currentSequence % length);
}
4、LeastActiveLoadBalance

LeastActiveLoadBalance 最小连接数负载均衡

/**
 * 最小活跃数算法实现:
 * 假定有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
 */
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
    int length = invokers.size(); // 计算可用invoker总数量
    // 最小的活跃数
    int leastActive = -1; // 所有可用服务中,最小的活跃数
    // 相同最小活跃数的服务提供者个数
    int leastCount = 0; 
    // 记录有相同最小活跃数的 Invoker 在 invokers 列表中的下标信息
    int[] leastIndexs = new int[length]; 
    //计算总权重
    int totalWeight = 0; 
    // 第一个最小活跃数的 Invoker 权重值,用于与其他具有相同最小活跃数的 Invoker 的权重进行对比,
    // 以检测是否所有具有相同最小活跃数的 Invoker 的权重均相等
    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);
        // 获取 Invoker 对应的活跃数
        int active = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName()).getActive(); 
        // 获取权重
        int weight = invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.WEIGHT_KEY, Constants.DEFAULT_WEIGHT); 
        // 发现更小的活跃数,重新开始
        if (leastActive == -1 || active < leastActive) { 
            // 使用当前活跃数 active 更新最小活跃数 leastActive
            leastActive = active; 
            // 更新 leastCount 为 1
            leastCount = 1; 
            // 记录当前下标值到 leastIndexs 中
            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?
            // 当前 Invoker 的活跃数 active 与最小活跃数 leastActive 相同
        } else if (active == leastActive) { // If current invoker's active value equals with leaseActive, then accumulating.
            // 在 leastIndexs 中记录下当前 Invoker 在 invokers 集合中的下标
            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?
            // 检测当前 Invoker 的权重与 firstWeight 是否相等,
            // 不相等则将 sameWeight 置为 false
            if (sameWeight && i > 0
                    && weight != firstWeight) {
                sameWeight = false;
            }
        }
    }
    // assert(leastCount > 0)  当只有一个 Invoker 具有最小活跃数,此时直接返回该 Invoker 即可
    if (leastCount == 1) {
        // If we got exactly one invoker having the least active value, return this invoker directly.
        return invokers.get(leastIndexs[0]);
    }
    // 有多个 Invoker 具有相同的最小活跃数,但它们之间的权重不同
    if (!sameWeight && totalWeight > 0) {
        // If (not every invoker has the same weight & at least one invoker's weight>0), select randomly based on totalWeight.
        // 随机生成一个 [0, totalWeight) 之间的数字
        int offsetWeight = random.nextInt(totalWeight);
        // Return a invoker based on the random value.
        // 循环让随机数减去具有最小活跃数的 Invoker 的权重值,
        // 当 offset 小于等于0时,返回相应的 Invoker
        for (int i = 0; i < leastCount; i++) {
            int leastIndex = leastIndexs[i];
            offsetWeight -= getWeight(invokers.get(leastIndex), invocation);
            // 有bug,假设有一组 Invoker 的权重为 5、2、1,假设 offsetWeight = 7,当 for 循环进行第二次遍历后 offsetWeight = 7 - 5 - 2 = 0,提前返回了。
            // 在2.6.5被修复了: int offsetWeight = random.nextInt(totalWeight) + 1;
            // 获取权重值,并让随机数减去权重值 - ⭐️
            if (offsetWeight <= 0)
                return invokers.get(leastIndex);
        }
    }
    // If all invokers have the same weight value or totalWeight=0, return evenly.
    // 如果权重相同或权重为0时,随机返回一个 Invoker
    return invokers.get(leastIndexs[random.nextInt(leastCount)]);
}
5、ConsistentHashLoadBalance

一致性hash负载均衡,虚拟出来160个虚拟节点,然后进行hash值落地

protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
    String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();
    // 基于 invokers 集合,根据对象内存地址来计算定义哈希值
    int identityHashCode = System.identityHashCode(invokers);
    // 获得 ConsistentHashSelector 对象
    ConsistentHashSelector<T> selector = (ConsistentHashSelector<T>) selectors.get(key);
    // 如果 invokers 是一个新的 List 对象,意味着服务提供者数量发生了变化,
    // 可能新增也可能减少了,此时 selector.identityHashCode != identityHashCode 条件成立
    if (selector == null || selector.identityHashCode != identityHashCode) {
        // 创建新的 ConsistentHashSelector
        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> {
    /**
     * 使用 TreeMap 存储 Invoker 虚拟节点   虚拟节点与 Invoker 的映射关系
     */
    private final TreeMap<Long, Invoker<T>> virtualInvokers;
    /**
     * 每个Invoker 对应的虚拟节点数
     */
    private final int replicaNumber;

    private final int identityHashCode;

    private final int[] argumentIndex;//存放需要做hash的参数在方法中的是属于第几个参数

    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();
        //每个 Invoker 对应的虚拟节点数,默认为 160
        // <dubbo:parameter key="hash.nodes" value="320" />
        this.replicaNumber = url.getMethodParameter(methodName, "hash.nodes", 160);
        // 获取参与 hash 计算的参数下标值,默认对第一个参数进行 hash 运算
        // <dubbo:parameter key="hash.arguments" value="0,1" />
        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]);
        }
        // 初始化 virtualInvokers
        for (Invoker<T> invoker : invokers) {
            String address = invoker.getUrl().getAddress();
            // 每四个虚拟结点为一组
            for (int i = 0; i < replicaNumber / 4; i++) {
                // Md5是一个16字节长度的数组,将16字节的数组每四个字节一组,
                // 分别对应一个虚拟结点
                // 对 address + i 进行 md5 运算,得到一个长度为16的字节数组
                byte[] digest = md5(address + i);
                for (int h = 0; h < 4; h++) {
                    // 对于每四个字节,组成一个long值,做为这个虚拟节点的在环中的惟一key
                    // h = 0 时,取 digest 中下标为 0 ~ 3 的4个字节进行位运算
                    // h = 1 时,取 digest 中下标为 4 ~ 7 的4个字节进行位运算
                    // h = 2, h = 3 时过程同上
                    long m = hash(digest, h);
                    // 将 hash 到 invoker 的映射关系存储到 virtualInvokers 中,
                    // virtualInvokers 需要提供高效的查询操作,因此选用 TreeMap 作为存储结构
                    virtualInvokers.put(m, invoker);
                }
            }
        }
    }

    //首先是对参数进行 md5 以及 hash 运算,得到一个 hash 值。然后再拿这个值到 TreeMap 中查找目标 Invoker 即可
    public Invoker<T> select(Invocation invocation) {
        // 将参数转为 key
        String key = toKey(invocation.getArguments());
        // 对参数 key 进行 md5 运算
        byte[] digest = md5(key);
        // 取 digest 数组的前四个字节进行 hash 运算,再将 hash 值传给 selectForKey 方法,
        // 寻找合适的 Invoker
        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) {
        // 到 TreeMap 中查找第一个节点值大于或等于当前 hash 的 Invoker
        Map.Entry<Long, Invoker<T>> entry = virtualInvokers.tailMap(hash, true).firstEntry();
        // 如果 hash 大于 Invoker 在圆环上最大的位置,此时 entry = null,
        // 需要将 TreeMap 的头节点赋值给 entry
       if (entry == null) {
          entry = virtualInvokers.firstEntry();
       }
       return entry.getValue();
    }

    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();
    }

}