dubbo3源码解析----负载均衡算法的实现

dubbo3源码解析----负载均衡算法的实现

负载均衡是开发中的重要模块,以 dubbo3 的源码为例,详细讲解负载均衡算法的实现

目前 Dubbo 内置了如下负载均衡算法,用户可直接配置使用:

算法特性备注
RandomLoadBalance加权随机默认算法,默认权重相同
RoundRobinLoadBalance加权轮询借鉴于 Nginx 的平滑加权轮询算法,默认权重相同
LeastActiveLoadBalance最少活跃优先 + 加权随机背后是能者多劳的思想
ShortestResponseLoadBalance最短响应优先 + 加权随机更加关注响应速度
ConsistentHashLoadBalance一致性 Hash确定的入参,确定的提供者,适用于有状态请求

都继承了AbstractLoadBalance类

AbstractLoadBalance抽象类分析

@SPI(RandomLoadBalance.NAME)
public interface LoadBalance {

    /**
     * select one invoker in list.
     *
     * @param invokers   invokers.
     * @param url        refer url
     * @param invocation invocation.
     * @return selected invoker.
     */
    @Adaptive("loadbalance")
    <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;

}


public abstract class AbstractLoadBalance implements LoadBalance {
    /**
     * Calculate the weight according to the uptime proportion of warmup time
     * the new weight will be within 1(inclusive) to weight(inclusive)
     *
     * @param uptime the uptime in milliseconds
     * @param warmup the warmup time in milliseconds
     * @param weight the weight of an invoker
     * @return weight which takes warmup into account
     */
    static int calculateWarmupWeight(int uptime, int warmup, int weight) {
      // 计算启动时的 比例的 warm 的 Weight
        int ww = (int) ( uptime / ((float) warmup / weight));
        return ww < 1 ? 1 : (Math.min(ww, weight));
    }

    @Override
    public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        if (CollectionUtils.isEmpty(invokers)) {
            return null;
        }
        if (invokers.size() == 1) {
            return invokers.get(0);
        }
      // 调子类的具体实现
        return doSelect(invokers, url, invocation);
    }

    protected abstract <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation);


    /**
     * Get the weight of the invoker's invocation which takes warmup time into account
     * if the uptime is within the warmup time, the weight will be reduce proportionally
     *
     * @param invoker    the invoker
     * @param invocation the invocation of this invoker
     * @return weight
     */
    protected int getWeight(Invoker<?> invoker, Invocation invocation) {
        int weight;
        URL url = invoker.getUrl();
        if (invoker instanceof ClusterInvoker) {
            url = ((ClusterInvoker<?>) invoker).getRegistryUrl();
        }

        // Multiple registry scenario, load balance among multiple registries.
      // 多注册表场景,多个注册表之间的负载平衡。
        if (REGISTRY_SERVICE_REFERENCE_PATH.equals(url.getServiceInterface())) {
          // 判断当前的服务接口是否为注册中心服务引用的服务接口
          // 直接返回 weight
            weight = url.getParameter(WEIGHT_KEY, DEFAULT_WEIGHT);
        } else {
          // 是在热启动中的服务
            weight = url.getMethodParameter(invocation.getMethodName(), WEIGHT_KEY, DEFAULT_WEIGHT);
            if (weight > 0) {
                long timestamp = invoker.getUrl().getParameter(TIMESTAMP_KEY, 0L);
                if (timestamp > 0L) {
                    long uptime = System.currentTimeMillis() - timestamp;
                    if (uptime < 0) {
                        return 1;
                    }
                    int warmup = invoker.getUrl().getParameter(WARMUP_KEY, DEFAULT_WARMUP);
                    if (uptime > 0 && uptime < warmup) {
                      // 根据启动时间的比例 计算一个WarmupWeight  
                        weight = calculateWarmupWeight((int)uptime, warmup, weight);
                    }
                }
            }
        }
        return Math.max(weight, 0);
    }
}

RandomLoadBalance

  • 加权随机算法的思路

比如我们有四台服务器,分别是A,B,C,D服务器。对应的权重分别是10,20,30,40。现在把这些权重值平铺在一维坐标值上,如下图所示

+-----------------------------------------------------------------------------------+
|          |               |                       |                              |
+-----------------------------------------------------------------------------------+
1          10              30                      60                            100

|-----A----|-------B-------|-----------C-----------|---------------D--------------|


-------------------15

---------------------------------37

--------------------------------------------------------------74

上面的图中一共有4块区域,长度分别是A,B,C和D的权重。然后从100个数中随机选择一个。然后再判断该数分布在哪个区域。比如,如果随机到37,37是分布在C区域的,那么就选择 Invoker C。15是在B区域,74是在D区域。

  • 执行次数越多,分布越均匀
public class RandomLoadBalance extends AbstractLoadBalance {

	// @SPI用
    public static final String NAME = "random";

    @Override
    protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        // invoker的数量
        int length = invokers.size();
        // 每个invoker的权重相同
        boolean sameWeight = true;
        // 每个invoker的权重的数组
        int[] weights = new int[length];
        // 第一个invoker的权重
        int firstWeight = getWeight(invokers.get(0), invocation);
        weights[0] = firstWeight;
        // 所有invoker的权重之和
        int totalWeight = firstWeight;
        for (int i = 1; i < length; i++) {
            int weight = getWeight(invokers.get(i), invocation);
            // 保存以待后用
            weights[i] = weight;
            // 计算所有invoker的权重之和
            totalWeight += weight;
            if (sameWeight && weight != firstWeight) {
                sameWeight = false;
            }
        }
        // 如果并非所有invoker都具有相同的权重,并且至少有一个invoker的权重大于0,根据totalWeight进行选择
        if (totalWeight > 0 && !sameWeight) {
        	// 根据总权重随机出一个偏移量
            int offset = ThreadLocalRandom.current().nextInt(totalWeight);
            // 根据随机值返回invoker
            for (int i = 0; i < length; i++) {
                offset -= weights[i];
                if (offset < 0) {
                    return invokers.get(i);
                }
            }
        }
        // 如果所有invoker都具有相同的权重值或totalWeight = 0,则平均返回。
        return invokers.get(ThreadLocalRandom.current().nextInt(length));
    }
}

RoundRobinLoadBalance

权重轮询负载均衡算法会根据设置的权重来判断轮循的比例。权重轮循分为普通权重轮询和平滑权重轮询。普通权重轮询会造成某个节点会被频繁选中,容易导致一个节点的流量在短时间内增加很多。平滑权重轮询会在轮询时会穿插选择其他的节点,让整个服务器的选择过程比较均匀,不会导致一个节点的流量暴增。

  • 加权轮询的定义:

但现实情况下,我们并不能保证每台服务器性能均相近。如果我们将等量的请求分配给性能较差的服务器,这显然是不合理的。因此,这个时候我们需要对轮询过程进行加权,以调控每台服务器的负载。经过加权后,每台服务器能够得到的请求数比例,接近或等于他们的权重比。比如服务器 A、B、C 权重比为 5:2:1。那么在8次请求中,服务器 A 将收到其中的5次请求,服务器 B 会收到其中的2次请求,服务器 C 则收到其中的1次请求。

public class RoundRobinLoadBalance extends AbstractLoadBalance {
    public static final String NAME = "roundrobin";

    private static final int RECYCLE_PERIOD = 60000;

  	// 内置类 => 封装WeightedRoundRobin
    protected static class WeightedRoundRobin {
        // invoker的权重值
        private int weight;
        // 考虑到并发场景下某个invoker会被同时选中,所以计算被所有线程选中的权重总和
        private AtomicLong current = new AtomicLong(0);
        // 最后一次更新时间
        private long lastUpdate;

        public int getWeight() {
            return weight;
        }

        public void setWeight(int weight) {
            this.weight = weight;
            current.set(0);
        }

        public long increaseCurrent() {
          // 原子在每次调用计算当前权重
            return current.addAndGet(weight);
        }

        public void sel(int total) {
            current.addAndGet(-1 * total);
        }

        public long getLastUpdate() {
            return lastUpdate;
        }

        public void setLastUpdate(long lastUpdate) {
            this.lastUpdate = lastUpdate;
        }
    }

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

    /**
     * get invoker addr list cached for specified invocation
     * 当前 invoker 的 轮询 addr list
     * <p>
     * <b>for unit test only</b>
     *
     * @param invokers
     * @param invocation
     * @return
     */
    protected <T> Collection<String> getInvokerAddrList(List<Invoker<T>> invokers, Invocation invocation) {
        String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();
        Map<String, WeightedRoundRobin> map = methodWeightMap.get(key);
        if (map != null) {
            return map.keySet();
        }
        return null;
    }

    @Override
    protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        // 远程调的 method key
        String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();
        //  获取 url 到 WeightedRoundRobin 映射表 没有 初始化 ConcurrentHashMap
        ConcurrentMap<String, WeightedRoundRobin> map = methodWeightMap.computeIfAbsent(key, k -> new ConcurrentHashMap<>());
        int totalWeight = 0;
        long maxCurrent = Long.MIN_VALUE;
        long now = System.currentTimeMillis();
        Invoker<T> selectedInvoker = null;
        WeightedRoundRobin selectedWRR = null;
        // 遍历 Invoker 列表
        for (Invoker<T> invoker : invokers) {
            String identifyString = invoker.getUrl().toIdentityString();
            // 获取 服务实例的 weight => 系统设置 或 默认 或根据启动时间热身的比例 weight
            int weight = getWeight(invoker, invocation);
            // 获取weightedRoundRobin 没有对应的就根据获得 weight 设置new weightedRoundRobin
            WeightedRoundRobin weightedRoundRobin = map.computeIfAbsent(identifyString, k -> {
                WeightedRoundRobin wrr = new WeightedRoundRobin();
                wrr.setWeight(weight);
                return wrr;
            });
						// 检测 Invoker 权重是否发生了变化,若变化了,则更新 WeightedRoundRobin 的 weight 字段
            if (weight != weightedRoundRobin.getWeight()) {
                //weight changed
                weightedRoundRobin.setWeight(weight);
            }
            // identifyString 对应的 weightedRoundRobin 增加 weight
            // 让 current 字段加上自身权重,等价于 current += weight
            long cur = weightedRoundRobin.increaseCurrent();
            // 设置 lastUpdate 字段,即 lastUpdate = now
            weightedRoundRobin.setLastUpdate(now);
            // 寻找具有最大 current 的 Invoker,以及 Invoker 对应的 WeightedRoundRobin,
            // 暂存起来,留作后用
            if (cur > maxCurrent) {
              // 替换 最大权重的 selectedInvoker
                maxCurrent = cur;
                selectedInvoker = invoker;
                selectedWRR = weightedRoundRobin;
            }
            // 计算权重总和
            totalWeight += weight;
        }
        if (invokers.size() != map.size()) {
           // 若WeightedRoundRobin未更新时长超过阈值后,就会被map移除掉,默认阈值为60秒。
            map.entrySet().removeIf(item -> now - item.getValue().getLastUpdate() > RECYCLE_PERIOD);
        }
        if (selectedInvoker != null) {
            //  让 current 减去权重总和
            selectedWRR.sel(totalWeight);
          // 返回 具有最大 current 的 选到的selectedInvoker
            return selectedInvoker;
        }
        // should not happen here
        return invokers.get(0);
    }

}

  • 下一次轮询的时候 会从已经维护好的methodWeightMap中直接取出对应的weightedRoundRobin,基于前一轮的维护过的 weight 再次自增,选出最大的 current 再减一次权重总和, 实现了平滑加权轮询算法

可以 用 5 2 1 模拟几次加权计算的算法,就能明白算法逻辑

(5) 2 1 => -3 2 1=>2 (4) 2 => 2 -4 2 => (7 ) -2 3 => -1 -2 3 => (4) 0 4 => -4 0 4 => 1 2 (5) => …

LeastActiveLoadBalance

最少活跃调用数算法的思想就是"活跃调用数越少,证明服务效率越高,单位时间内可以处理的请求就越多"。每一个服务提供者对应一个active,初始值都是0,每增加一次请求就+1,每完成一次请求就-1。在服务运行一段时间之后,性能越好的服务的active就越小,处理请求的速度就越快,所以获得新请求的机会就越大,这就是最少活跃数负载均衡算法的基本思想。

LeastActiveLoadBalance在最少活跃数负载均衡算法的基础上增加权重的概念。举个例子来说明:有两个性能非常好的服务器,在某一时间active是相同的,此时 Dubbo 会根据它们的权重去分配请求,权重越大,获取到新请求的概率就越大。如果两个服务提供者权重相同,此时随机选择一个即可。

  • 算法理解: 节点的活跃数维护在RpcStatus内, active 的值是维护在调用前后增减

①遍历 invokers 列表,寻找活跃数最小的Invok

②如果有多个Invoker具有相同的最小活跃数,此时记录下这些Invoker在invokers 集合中的下标,并累加它们的权重,比较它们的权重值是否相等。=> 用于加权随机选择节点

③如果只有一个Invoker具有最小的活跃数,此时直接返回该Invoker即可

④如果有多个Invoker具有最小活跃数,且它们的权重不相等 => RandomLoadBalance 一致。

⑤如果有多个Invoker具有最小活跃数,但它们的权重相等,此时随机返回一个即可。

public class RpcStatus {

    private static final ConcurrentMap<String, RpcStatus> SERVICE_STATISTICS = new ConcurrentHashMap<String,
            RpcStatus>();

    private static final ConcurrentMap<String, ConcurrentMap<String, RpcStatus>> METHOD_STATISTICS =
            new ConcurrentHashMap<String, ConcurrentMap<String, RpcStatus>>();

    private final ConcurrentMap<String, Object> values = new ConcurrentHashMap<String, Object>();

    private final AtomicInteger active = new AtomicInteger();
    private final AtomicLong total = new AtomicLong();
    private final AtomicInteger failed = new AtomicInteger();
    private final AtomicLong totalElapsed = new AtomicLong();
    private final AtomicLong failedElapsed = new AtomicLong();
    private final AtomicLong maxElapsed = new AtomicLong();
    private final AtomicLong failedMaxElapsed = new AtomicLong();
    private final AtomicLong succeededMaxElapsed = new AtomicLong();
  ....

}

public class LeastActiveLoadBalance extends AbstractLoadBalance {

    public static final String NAME = "leastactive";

    @Override
    protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        // invoker的数量
        int length = invokers.size();
        // 所有invoker的最小活跃数
        int leastActive = -1;
        // 具有相同“最小活跃数”的服务者提供者
        int leastCount = 0;
        // 记录具有相同“最小活跃数”的 Invoker 在 invokers 列表中的下标信息
        int[] leastIndexes = new int[length];
        // 每一个invoker的权重
        int[] weights = new int[length];
        // 所有invoker的权重之和
        int totalWeight = 0;
        // 第一个最小活跃数的 Invoker 权重值,用于与其他具有相同最小活跃数的 Invoker 的权重进行对比,
        int firstWeight = 0;
        // 以检测是否“所有具有相同最小活跃数的 Invoker 的权重”均相等
        boolean sameWeight = true;


        // 开始遍历所有invokers
        for (int i = 0; i < length; i++) {
            Invoker<T> invoker = invokers.get(i);
            // 获取当前索引的invoker的活跃数
            int active = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName()).getActive();
            // 获取权重,默认是100
            int afterWarmup = getWeight(invoker, invocation);
            // 保存后面使用
            weights[i] = afterWarmup;
            // 如果是第一个invoker,或者当前invoker的active小于最小活跃数
            if (leastActive == -1 || active < leastActive) {
                // 将当前invoker的active赋值给最小活跃数
                leastActive = active;
                // 更新leastCount为1
                leastCount = 1;
                // 记录当前下标值到 leastIndexs 中
                leastIndexes[0] = i;
                // 重置 totalWeight
                totalWeight = afterWarmup;
                // 记录第一个最小活跃数的 invoker 权重值
                firstWeight = afterWarmup;
                // 每一个invoker都有相同的权重(这里只有一个调用)
                sameWeight = true;
                // 如果当前invoker的active等于最小活跃数,则累加
            } else if (active == leastActive) {
                // 在 leastIndexs 中记录下当前 Invoker 在 invokers 集合中的下标
                leastIndexes[leastCount++] = i;
                // 累加权重
                totalWeight += afterWarmup;
                // 检测当前 Invoker 的权重与 firstWeight 是否相等,不相等则将 sameWeight 置为 false
                if (sameWeight && afterWarmup != firstWeight) {
                    sameWeight = false;
                }
            }
        }
        // 当只有一个 Invoker 具有最小活跃数,此时直接返回该 Invoker 即可
        if (leastCount == 1) {
            return invokers.get(leastIndexes[0]);
        }
        // 有多个 invoker 具有相同的最小活跃数,但它们之间的权重不同
        if (!sameWeight && totalWeight > 0) {
            // 生成一个随机数
            int offsetWeight = ThreadLocalRandom.current().nextInt(totalWeight);
            // 循环让随机数减去具有最小活跃数的 Invoker 的权重值,
            // 当 offset 小于等于0时,返回相应的 Invoker
            for (int i = 0; i < leastCount; i++) {
                int leastIndex = leastIndexes[i];
                // 获取权重值,并让随机数减去权重值
              	// 计算随机值在哪两个个节点的权重值之间
                offsetWeight -= weights[leastIndex];
                if (offsetWeight < 0) {
                    return invokers.get(leastIndex);
                }
            }
        }
        // 如果权重相同或权重为0时,随机返回一个 Invoker
        return invokers.get(leastIndexes[ThreadLocalRandom.current().nextInt(leastCount)]);
    }
}

ConsistentHashLoadBalance

算法设计的目的:

一致性 Hash,相同参数的请求总是发到同一提供者。当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。

  • 缺省只对第一个参数Hash,如果要修改,请配置 <dubbo:parameter key=“hash.arguments” value=“0,1” />
  • 缺省用 160 份虚拟节点,如果要修改,请配置 <dubbo:parameter key=“hash.nodes” value=“320” />

Hash算法的定义

把任意长度的输入,通过Hash算法变换成固定长度的输出,这个输出就是Hash值。哈希值的空间远小于输入的空间,所以可能会发生“哈希碰撞”,即两个不同的输入,产生了同一个输出。

Hash算法只是一个定义,并没有规定具体的实现。比如我们常见的MD5、SHA都属于Hash算法的实现。

一致性Hash算法的定义

使用一致性Hash可以解决因为横向伸缩导致的大规模数据变动。

先对服务器节点的IP进行Hash,然后取模得到服务器节点在这个Hash环中的位置,然后并将这个Hash投射到 [0, 2^32 - 1] 的圆环上。

在这里插入图片描述

假设这个时候来了一个请求,先对这个请求进行Hash求值,然后取模求余,然后顺时针找到第一个节点,那么这个节点就是负责处理请求的节点。比如请求①和②就是节点A负责处理,③就是节点B负责处理,④就是节点C负责处理。

在这里插入图片描述

但是一致性Hash也存在一定的局限性。假如节点很少的情况下,一致性Hash就会出现分布不均匀的情况。如下所示,①②③④⑥的请求都会落在A节点上,这个时候就会对A节点增加过多的符合。

在这里插入图片描述

那么这个时候提出了虚拟节点的概念,通过虚拟节点均衡各个节点的请求量。所谓虚拟节点,就是除了对服务本身地址进行Hash映射外,还通过在它地址上做些处理(比如Dubbo中,在ip+port的字符串后加上计数符1、2、3…,分别代表虚拟节点1、2、3),以达到同一服务映射多个节点的目的。

在这里插入图片描述

算法理解

粗暴的理解

  • 对 ip 地址 hash算法 出多个虚拟节点的 int值,作为 key 插入到一个 hashMap 中, 值为 ip 节点;

    • 不同权重的节点可以增加不同数量的虚拟节点
  • 再保存一个 ip 地址为key 的 hashMap,存储期 hashKey => 环调整时即主要调整这两个 map 的映射关系

    • 取决于是否需要维护这层关系
  • 取节点时,在随机一个int 值,在 hashMap 的 key 中找到第一个大于它的虚拟节点值,找到其 ip 节点

代码分析

  • 重点在于怎么构建一致性 hash 环

/**
 * ConsistentHashLoadBalance
 */
public class ConsistentHashLoadBalance extends AbstractLoadBalance {
    public static final String NAME = "consistenthash";

    /**
     * Hash nodes name
     */
    public static final String HASH_NODES = "hash.nodes";

    /**
     * Hash arguments name
     */
    public static final String HASH_ARGUMENTS = "hash.arguments";

    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 methodName = RpcUtils.getMethodName(invocation);
        String key = invokers.get(0).getUrl().getServiceKey() + "." + methodName;
        // using the hashcode of list to compute the hash only pay attention to the elements in the list
        int invokersHashCode = invokers.hashCode();
      	// 获取当前 key 对应的 一致性 hash 环选择器
        ConsistentHashSelector<T> selector = (ConsistentHashSelector<T>) selectors.get(key);
        if (selector == null || selector.identityHashCode != invokersHashCode) {
          // 当selector 或者 selector的标志 hashcode 不等于 invokersHashCode时
          // 重新构建一致性 hash 环选择器,并放回selectors中
            selectors.put(key, new ConsistentHashSelector<T>(invokers, methodName, invokersHashCode));
            selector = (ConsistentHashSelector<T>) selectors.get(key);
        }
      // 从  一致性 hash 环选择器 选择节点
        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) {
          // 一致性 hash 环 TreeMap
            this.virtualInvokers = new TreeMap<Long, Invoker<T>>();
            this.identityHashCode = identityHashCode;
            URL url = invokers.get(0).getUrl();
            // 虚拟节点总数 
            this.replicaNumber = url.getMethodParameter(methodName, HASH_NODES, 160);
          // 读取配置的 要进入 key -- hash 计算的参数个数
            String[] index = 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++) {
                    // 对每个节点 计算 digest 
                    byte[] digest = Bytes.getMD5(address + i);
                    for (int h = 0; h < 4; h++) {
                        // 根据digest 和 h 计算虚拟节点值
                        long m = hash(digest, h);
                        // 放入 环中
                        virtualInvokers.put(m, invoker);
                    }
                }
            }
        }

        public Invoker<T> select(Invocation invocation) {
          // 用参数计算一个取环节点的 hash 值
            String key = toKey(invocation.getArguments());
            byte[] digest = Bytes.getMD5(key);
            return selectForKey(hash(digest, 0));
        }

        private String toKey(Object[] args) {
          // 拼接参数到 str
            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) {
          // 用 hash 值取出一个 entry 返回
          // ceilingEntry : 在树中找第一个大于它的节点
            Map.Entry<Long, Invoker<T>> entry = virtualInvokers.ceilingEntry(hash);
            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;
        }
    }

}

// 树中查找的操作 
   final Entry<K,V> getCeilingEntry(K key) {
        Entry<K,V> p = root;
        while (p != null) {
            int cmp = compare(key, p.key);
            if (cmp < 0) {
                if (p.left != null)
                    p = p.left;
                else
                    return p;
            } else if (cmp > 0) {
                if (p.right != null) {
                    p = p.right;
                } else {
                    Entry<K,V> parent = p.parent;
                    Entry<K,V> ch = p;
                    while (parent != null && ch == parent.right) {
                        ch = parent;
                        parent = parent.parent;
                    }
                    return parent;
                }
            } else
                return p;
        }
        return null;
    }

参考链接:

Dubbo进阶(十二):负载均衡 https://juejin.cn/post/6844904192788660232

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值