ダボでのいくつかの負荷分散アルゴリズムの簡単な理解


序文

マイクロサービスレベルでは、ロードバランシングは主にサーバーのプレッシャーの問題を解決し、ロードバランシングアルゴリズムを通じてリクエストを異なるマイクロサービスに割り当てることができ、特定のマイクロサービスが過度のリクエストプレッシャーによって引き起こされるサービス崩壊の問題を解決します。この記事では、dubbo のいくつかの負荷分散アルゴリズムを分析します. dubbo での負荷分散アルゴリズムの実装を理解することで、負荷分散アルゴリズムの核となる考え方に慣れることができます.


Dubbo は負荷分散のための抽象インターフェース LoadBalance を提供します. 全部で 5 つの実装があります.

  • コンシステント ハッシュ アルゴリズム: ConsistentHashLoadBalance。
  • 最小アクティビティ アルゴリズム: LeastActiveLoadBalance。
  • ダボのデフォルトの負荷分散アルゴリズムである加重ランダム: RandomLoadBalance。
  • ラウンド ロビン アルゴリズム: RoundRobinLoadBalance。
  • 最短応答時間アルゴリズム: ShortestResponseLoadBalance。

ダボで独自のロード バランシング アルゴリズムを使用する場合は、このインターフェイスを実装し、そのメソッドを書き換えてから、ダボ spi に従って実装を構成するだけで済みます。どのロード バランシング アルゴリズムを使用するか。

  • LoadBalance: 負荷分散のための抽象インターフェース。
@SPI(RandomLoadBalance.NAME)
public interface LoadBalance {
    
    

    @Adaptive("loadbalance")
    <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;
}
  • AbstractLoadBalance: 負荷分散抽象インターフェイス LoadBalance の抽象実装。
public abstract class AbstractLoadBalance implements LoadBalance {
    
    
    static int calculateWarmupWeight(int uptime, int warmup, int weight) {
    
    
        int ww = (int) (Math.round(Math.pow((uptime / (double) warmup), 2) * 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);

    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 = 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) {
    
    
                        weight = calculateWarmupWeight((int)uptime, warmup, weight);
                    }
                }
            }
        }
        return Math.max(weight, 0);
    }
}

コンシステント ハッシュ アルゴリズム: ConsistentHashLoadBalance

分散システムでは、コンシステント ハッシュ アルゴリズムが要求の一部を指定されたマシンに固定し、バランスのとれた分散の効果を達成できます。コンシステント ハッシュ アルゴリズムにはハッシュ リングという概念があります. ダボではハッシュ リングを実現するために TreeMap を使用します. TreeMap の特徴は小さなものから大きなものまでキーを格納することです.

ソースコード分析:
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;
    // 计算整个节点集合list的hashcode,目的在于判断集合中的元素是否发生了变化。
    int invokersHashCode = getCorrespondingHashCode(invokers);
    ConsistentHashSelector<T> selector = (ConsistentHashSelector<T>) selectors.get(key);
    // 如果根据key取出来的selector是空的,或者hashcode已经发生了变化,就说明节点信息已经发生了变化,那么就需要重新构造hash环。
    if (selector == null || selector.identityHashCode != invokersHashCode) {
    
    
        selectors.put(key, new ConsistentHashSelector<T>(invokers, methodName, invokersHashCode));
        selector = (ConsistentHashSelector<T>) selectors.get(key);
    }
    // 选择节点
    return selector.select(invocation);
}

// 构造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();
    this.replicaNumber = url.getMethodParameter(methodName, HASH_NODES, 160);
    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]);
    }
    // 循环所有的节点信息,构造hash环。
    for (Invoker<T> invoker : invokers) {
    
    
        String address = invoker.getUrl().getAddress();
        // replicaNumber默认为160,这里会根据每一个节点的ip生成40个虚拟节点。
        for (int i = 0; i < replicaNumber / 4; i++) {
    
    
            byte[] digest = Bytes.getMD5(address + i);
            // 为每一个虚拟节点生成四个存储位置
            for (int h = 0; h < 4; h++) {
    
    
                long m = hash(digest, h);
                // key就是当前的节点的ip计算出来的hash值,value就是当前循环的节点。
                virtualInvokers.put(m, invoker);
            }
        }
    
    totalRequestCount = new AtomicLong(0);
    serverCount = invokers.size();
    erRequestCountMap.clear();
}

// 最终调用这个方法进行选择节点
private Invoker<T> selectForKey(long hash) {
    
    
    // 参数hash就是根据hash参数计算出来的下标,这里根据下标获取大于等于该hash值的最小节点信息
    Map.Entry<Long, Invoker<T>> entry = virtualInvokers.ceilingEntry(hash);
    // 如果节点信息不存在,那么说明当前hash值就是hash环上最大的那么节点信息,
    if (entry == null) {
    
    
        // 那么接下来的那个下标就是hash环上的第一个元素了。
        entry = virtualInvokers.firstEntry();
    }
	// 取出节点信息的地址
    String serverAddress = entry.getValue().getUrl().getAddress();

    // 这里设定了一个请求线程数阈值
    double overloadThread = ((double) totalRequestCount.get() / (double) serverCount) * OVERLOAD_RATIO_THREAD;
    // 如果当前选定的这个节点已经接收过请求并且接收的请求数超过了设定的这个线程数阈值,说明这个节点不可用
    while (serverRequestCountMap.containsKey(serverAddress)
            && serverRequestCountMap.get(serverAddress).get() >= overloadThread) {
    
    
        // 如果这个节点不可用,就需要获取大于给定的key的最小节点信息
        entry = getNextInvokerNode(virtualInvokers, entry);
        // 重新获取节点信息的地址
        serverAddress = entry.getValue().getUrl().getAddress();
    }
    // 当前选定的这个节点没有接收过请求,那么就说明可用,将当前节点信息假如到serverRequestCountMap中
    if (!serverRequestCountMap.containsKey(serverAddress)) {
    
    
        serverRequestCountMap.put(serverAddress, new AtomicLong(1));
    } else {
    
    
        // 接收的请求数未超过设定的这个线程数阈值,那么就说明可用,将当前节点信息已经接收的请求数加1
        serverRequestCountMap.get(serverAddress).incrementAndGet();
    }
    totalRequestCount.incrementAndGet();
    return entry.getValue();
}
要約:
  1. まずサービスリスト全体のハッシュコード値を計算し、サービスリストのハッシュコード値に応じてハッシュリングの再構築が必要かどうかを判断し、ハッシュリングを構築すると、各ノードのIPに応じて40個の仮想ノードが生成されます、および各仮想ノードは 4 つの場所に格納されます。仮想ノードの導入は、実際のノードのデータが少ないために発生するデータ スキューの問題を解決するためのものです。
  2. リクエストで指定されたパラメータ値に従ってハッシュ値を計算し、ハッシュリングからノード情報を取得します。
  3. 取得したノード情報に従って、現在のノードが以前にリクエストを受信したことがあるかどうかを計算します。
  4. リクエストを受信し、受信したリクエスト数が設定したリクエスト数の閾値を超えた場合、現在選択されているノードは使用不可とみなされ、次のノード情報が引き続き選択されます。
  5. 現在選択されているノードがリクエストを受信して​​いない場合は、そのノードが使用可能であることを意味し、現在のノード情報が serverRequestCountMap に格納されている場合、または受信したリクエストの数が設定されたスレッド数のしきい値を超えていない場合は、使用可能であることを意味します。現在のノード情報 受信したリクエストの数を 1 増やします。
  6. 最後に、選択されたノードを返します。

最小アクティビティ: LeastActiveLoadBalance

サービスが要求を処理する頻度に基づいて選択します。頻度が低いほど、より適しています。

ソースコード分析
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
    
    
    // 记录服务数量
    int length = invokers.size();
    // 用来记录所有服务中,最小活跃度最低的那个服务的最小活跃数
    int leastActive = -1;
    // 具有相同最小活跃数的服务个数
    int leastCount = 0;
    // 具有最小活跃数的服务集合
    int[] leastIndexes = new int[length];
    // 每一个服务的权重集合
    int[] weights = new int[length];
    // 所有服务总权重和
    int totalWeight = 0;
    // 第一个最小活跃数的服务的权重,类似于选中了一个标准,之后用这个标准和每一个服务的权重进行比较,用来判断是否所有的服务的具有相同的权重。
    int firstWeight = 0;
    // 标志是否所有的服务的具有相同的权重,默认为true
    boolean sameWeight = true;

    // 循环所有的服务
    for (int i = 0; i < length; i++) {
    
    
        Invoker<T> invoker = invokers.get(i);
        // 获取当前服务的活跃数
        int active = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName()).getActive();
        // 获取当前服务的权重,默认为100
        int afterWarmup = getWeight(invoker, invocation);
        // 将当前服务的权重按照序号加入到weights数组中。
        weights[i] = afterWarmup;
        // 如果当前服务是第一个服务或者当前服务的活跃数比之前记录的最小活跃数还小
        if (leastActive == -1 || active < leastActive) {
    
    
            // 当前服务的活跃数为最小活跃数
            leastActive = active;
            // 具有相同最小活跃数的服务个数,也就是当前服务器的个数
            leastCount = 1;
            // 将当前服务按照序号加入到具有相同最小活跃数的服务集合中
            leastIndexes[0] = i;
            // 当前服务的权重就是权重之和
            totalWeight = afterWarmup;
            // 当前服务的权重就是第一个最小活跃数的服务的权重
            firstWeight = afterWarmup;
            // 这种情况下,所有的服务的权重都是一样的。
            sameWeight = true;
            // 如果当前服务的活跃数和之前记录的最小活跃数是一样的
        } else if (active == leastActive) {
    
    
            // 当前服务加入到具有最小活跃数的服务集合中,具有相同最小活跃数的服务个数加1
            leastIndexes[leastCount++] = i;
            // 总权重加上当前服务的权重
            totalWeight += afterWarmup;
            // 如果所有的服务的权重都是一样的,并且当前服务的权重和第一个最小活跃数的服务的权重不相等
            if (sameWeight && afterWarmup != firstWeight) {
    
    
                // 说明所有的服务的权重不一样的
                sameWeight = false;
            }
        }
    }
    // 如果上边的所有的流程完了之后,只有一个最小活跃数的服务
    if (leastCount == 1) {
    
    
        // 那么就直接将这个服务返回即可,这个服务就是目前最小活跃数的服务
        return invokers.get(leastIndexes[0]);
    }
    // 如果所有的服务的权重不一样,并且总权重之和大于0
    if (!sameWeight && totalWeight > 0) {
    
    
        // 随机从总权重中获取一个数
        int offsetWeight = ThreadLocalRandom.current().nextInt(totalWeight);
        // 循环具有相同最小活跃数的服务集合
        for (int i = 0; i < leastCount; i++) {
    
    
            int leastIndex = leastIndexes[i];
            // 从权重集合中获取当前服务的权重,并用随机权重减去这个服务的权重
            offsetWeight -= weights[leastIndex];
            // 如果随机权重减去这个服务的权重之后小于0,那么就表明这个服务就是符合条件的服务。
            if (offsetWeight < 0) {
    
    
                return invokers.get(leastIndex);
            }
        }
    }
    // 如果所有服务的权重都是一样的,并且总权重为0,那么随机选取一个服务即可
    return invokers.get(leastIndexes[ThreadLocalRandom.current().nextInt(leastCount)]);
}
要約:
  1. すべてのサービスから最小アクティブ数が同じサービスを見つけ、これらのサービスの重みセットとサービスの数を記録し、すべてのサービスの重みが同じかどうかを判断します。
  2. すべてのサービスの重みが同じである場合、見つかったサービスから同じ最小アクティブ数を持つサービスをランダムに返すことができます。
  3. すべてのサービスの重みが異なり、最小アクティブ数が同じサービスが 1 つしかない場合は、このサービスを直接返します。
  4. すべてのサービスの重みが同じではなく、同じ最小アクティブ数を持つ複数のサービスがある場合、合計重みに従って乱数が取得され、同じ最小アクティブ数を持つ見つかった各サービスの重みから差し引かれます。取得した値が 0 未満の場合、現在のサービスは適格なサービスであり、単に戻ります。

加重ランダム: RandomLoadBalance (ダボのデフォルトの負荷分散アルゴリズム)

重み付きランダム アルゴリズムは、dubbo のデフォルトの負荷分散アルゴリズムであり、重みを使用してサービス リストからサービスをランダムに取得します。

ソースコード分析
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
    
    
    // 获取远程服务的个数
    int length = invokers.size();
    // 这是一个标识,标识所有的远程服务的权重是否都是一样的,后续再遍历所有的远程服务的时候,只要有一个远程服务的权重和其他的不一致,该标志就会被改为false,默认为true。
    boolean sameWeight = true;
    // 用来存放各个远程服务的权重
    int[] weights = new int[length];
    // 总权重,各个远程服务的权重之和。
    int totalWeight = 0;
    // 循环所有的远程服务
    for (int i = 0; i < length; i++) {
    
    
        // 获取当前远程服务的权重
        int weight = getWeight(invokers.get(i), invocation);
        // 计算所有的远程服务权重之和
        totalWeight += weight;
        // 保存当前远程服务的权重
        weights[i] = totalWeight;
        // 旧版本的是注释的这种写法,容易理解点:就是当前远程服务的权重和上一个远程服务的权重进行比较,如果不一样,就将sameWeight改为false,新版的这种写法不容易理解。
        /**
        if (sameWeight && i > 0
                    && weight != getWeight(invokers.get(i - 1), invocation)) {
                sameWeight = false;
        }
        **/
        if (sameWeight && totalWeight != weight * (i + 1)) {
    
    
            sameWeight = false;
        }
    }
    // 如果总权重 > 0 并且所有的远程服务的权重都不一样
    if (totalWeight > 0 && !sameWeight) {
    
    
        // 随机从总权重中获取一个数字。
        int offset = ThreadLocalRandom.current().nextInt(totalWeight);
        // 再次循环所有的远程服务集合
        for (int i = 0; i < length; i++) {
    
    
            // 如果随机数小于当前远程服务的权重,那么随机数刚好落在当前远程服务所占有的权重区间。
            if (offset < weights[i]) {
    
    
                return invokers.get(i);
            }
        }
    }
    // 如果总权重 <= 0 或者所有的远程服务的权重都一样,那么随机选取一个远程服务
    return invokers.get(ThreadLocalRandom.current().nextInt(length));
}
要約:
  1. すべてのサービス ノードの数を計算し、この数をループして各ノードの重みを取得し、すべてのノードの重みが同じかどうかを確認します。
  2. すべてのノードの重みが同じ場合は、ノードをランダムに返します。
  3. 各ノードの重みが異なる場合は、ノードの重みの合計を計算し、ループ ノードの順序で各ノードの重みをキャッシュします。
  4. 重みの合計内でランダムなデータをランダムに生成し、重みとキャッシュを循環させます. 重みの合計より小さい乱数を持つ添字が、現在選択されているノードです.

ラウンド ロビン アルゴリズム: RoundRobinLoadBalance

ポーリング アルゴリズムの考え方は、毎回サービスを選択することであり、このサービスは最後の選択と矛盾している必要があります。

ソースコード分析:
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
    
    
    // key就是接口全限定名.方法名
    String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();
    // 处理RoundRobinLoadBalance自身的缓存,key为接口全限定名.方法名,value为mao<每一个服务的标识identifyString,封装的WeightedRoundRobin>
    ConcurrentMap<String, WeightedRoundRobin> map = methodWeightMap.computeIfAbsent(key, k -> new ConcurrentHashMap<>());
    // 总权重
    int totalWeight = 0;
    // 设置的最大值,默认为long的最小值,为负数
    long maxCurrent = Long.MIN_VALUE;
    long now = System.currentTimeMillis();
    // 选中的服务
    Invoker<T> selectedInvoker = null;
    // 选中服务对应的WeightedRoundRobin信息
    WeightedRoundRobin selectedWRR = null;
    // 开始循环所有服务
    for (Invoker<T> invoker : invokers) {
    
    
        // 获取当前服务的标识
        String identifyString = invoker.getUrl().toIdentityString();
        // 获取当前服务的权重,默认为100
        int weight = getWeight(invoker, invocation);
        // 处理缓存,如果存在当前服务的WeightedRoundRobin信息,就返回当前服务的WeightedRoundRobin,如果没有,就构造一个WeightedRoundRobin,并将当前服务的权重设置进去。
        WeightedRoundRobin weightedRoundRobin = map.computeIfAbsent(identifyString, k -> {
    
    
            WeightedRoundRobin wrr = new WeightedRoundRobin();
            wrr.setWeight(weight);
            return wrr;
        });
		// 判断下,是不是权重发生了变化,如果发生了变化,需要重新设置
        if (weight != weightedRoundRobin.getWeight()) {
    
    
            weightedRoundRobin.setWeight(weight);
        }
        // 实际上这里获取的就是当前这个服务的对应的权重,但是这个值在后续还会处理,这个值会直接影响服务能不能被选中。
        long cur = weightedRoundRobin.increaseCurrent();
        // 设置对应的服务信息最后更新时间为当前时间
        weightedRoundRobin.setLastUpdate(now);
        // 如果cur大于maxCurrent,当前服务被选中,相当于在找最大权重的那个服务
        if (cur > maxCurrent) {
    
    
            // 设置cur为maxCurrent
            maxCurrent = cur;
            // 记录选中的服务
            selectedInvoker = invoker;
            // 记录选中的服务的其他信息
            selectedWRR = weightedRoundRobin;
        }
        // 记录总权重
        totalWeight += weight;
    }
    // 处理RoundRobinLoadBalance中自身的缓存,如果缓存中的服务信息和服务列表中的信息不一致,那么就需要从缓存中将超过1分钟还没有被更新的服务移除掉。
    if (invokers.size() != map.size()) {
    
    
        map.entrySet().removeIf(item -> now - item.getValue().getLastUpdate() > RECYCLE_PERIOD);
    }
    // 如果选中的服务不为空
    if (selectedInvoker != null) {
    
    
        // 这里会将当前选中的服务信息的cur设置成一个负数,也就是设置成所有权重的之和的负数,那么这个服务的cur就是最小的一个了,目的是为了保证下一次不会再次选中该服务
        selectedWRR.sel(totalWeight);
        return selectedInvoker;
    }
    // 如果上述流程之后,还未选中服务,那么直接就返回第一个服务。
    return invokers.get(0);
}
要約:
  1. RoundRobinLoadBalance は、サービス リストの選択情報を自身でキャッシュし、実際のサービス リスト情報が変更されると、自身のキャッシュ情報を更新、つまり、キャッシュ情報でサービス情報が 1 分以上更新されていないサービスを削除します。
  2. 呼び出しごとに、サービス リストに移動して重みが最も高いサービスを見つけ、見つかった場合は記録し、見つからない場合は最初のサービスに直接戻ります。
  3. サービス リストが循環されると、サービス時間が最初に更新され、値 cur が設定されます。この値は非常に重要であり、現在のサービスを選択できるかどうかを直接決定します。選択されたサービスは後で cur 値を次のように設定します。次のポーリングが再度選択されないことが保証されます。

最短応答時間アルゴリズム: ShortestResponseLoadBalance

ソースコード分析:
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
    
    
    // 获取远程服务的个数
    int length = invokers.size();
    // 最大的响应时间,默认为long的最大值
    long shortestResponse = Long.MAX_VALUE;
    // 具有相同最短响应时间的服务的个数
    int shortestCount = 0;
    // 具有相同最短响应时间的服务的集合
    int[] shortestIndexes = new int[length];
    // 每一个服务的权重集合
    int[] weights = new int[length];
    // 所有服务总权重和
    int totalWeight = 0;
    // 第一个最短响应时间的服务的权重,类似于选中了一个标准,之后用这个标准和每一个服务的权重进行比较,用来判断是否所有的服务的具有相同的权重。
    int firstWeight = 0;
    // 标志是否所有的服务的具有相同的权重,默认为true
    boolean sameWeight = true;

    // 循环所有的服务
    for (int i = 0; i < length; i++) {
    
    
        Invoker<T> invoker = invokers.get(i);
        // 获取当前服务的rpc信息
        RpcStatus rpcStatus = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName());
        // 获取当前服务成功响应所花费的平均响应时间
        long succeededAverageElapsed = rpcStatus.getSucceededAverageElapsed();
        //  获取当前服务的活跃数,加1的目的就是算上本次请求。
        int active = rpcStatus.getActive() + 1;
        // 计算处理完这些请求需要花费的总体响应时间
        long estimateResponse = succeededAverageElapsed * active;
        // 获取当前服务的权重
        int afterWarmup = getWeight(invoker, invocation);
        // 按照序号记录权重
        weights[i] = afterWarmup;
        // 如果当前服务处理完这些请求需要花费的总体响应时间小于设置最大的响应时间,这里实际上就是在找最小响应时间的服务。
        if (estimateResponse < shortestResponse) {
    
    
            // 设置最大的响应时间为当前服务的总体响应时间
            shortestResponse = estimateResponse;
            // 最短响应时间的数量为1,也就是本服务一个
            shortestCount = 1;
            // 记录最短响应时间的这个服务
            shortestIndexes[0] = i;
            // 总权重就是当前这个服务的权重
            totalWeight = afterWarmup;
            // 当前服务的权重就是第一个最短响应时间的服务的权重
            firstWeight = afterWarmup;
            // 这种情况下,所有的服务的权重都是一样的。
            sameWeight = true;
            // 如果当前服务的最短响应时间和之前记录的最短响应时间是一样的
        } else if (estimateResponse == shortestResponse) {
    
    
            // 当前服务加入到具有最短响应时间的服务集合中,具有相同最短响应时间的服务个数加1
            shortestIndexes[shortestCount++] = i;
            // 总权重加上当前服务的权重
            totalWeight += afterWarmup;
            // 如果所有的服务的权重都是一样的,并且当前服务不是列表中第一个服务,并且当前服务的权重和第一个最短响应时间的服务的权重不相等
            if (sameWeight && i > 0
                    && afterWarmup != firstWeight) {
    
    
                // 说明所有的服务的权重不一样的
                sameWeight = false;
            }
        }
    }
    // 如果上边的所有的流程完了之后,只有一个最短响应时间的服务
    if (shortestCount == 1) {
    
    
        // 那么就直接将这个服务返回即可,这个服务就是目前最短响应时间的服务
        return invokers.get(shortestIndexes[0]);
    }
    // 如果所有的服务的权重不一样,并且总权重之和大于0
    if (!sameWeight && totalWeight > 0) {
    
    
        // 随机从总权重中获取一个数
        int offsetWeight = ThreadLocalRandom.current().nextInt(totalWeight);
        // 循环具有相同最短响应时间的服务集合
        for (int i = 0; i < shortestCount; i++) {
    
    
            int shortestIndex = shortestIndexes[i];
            // 从权重集合中获取当前服务的权重,并用随机权重减去这个服务的权重
            offsetWeight -= weights[shortestIndex];
            // 如果随机权重减去这个服务的权重之后小于0,那么就表明这个服务就是符合条件的服务。
            if (offsetWeight < 0) {
    
    
                return invokers.get(shortestIndex);
            }
        }
    }
    // 如果所有服务的权重都是一样的,并且总权重为0,那么随机选取一个服务即可
    return invokers.get(shortestIndexes[ThreadLocalRandom.current().nextInt(shortestCount)]);
}
要約:
  1. すべてのサービスから応答時間が最も短いサービスを見つけ、これらのサービスの重みセットとサービスの数を記録し、すべてのサービスの重みが同じかどうかを判断します。
  2. すべてのサービスの重みが同じである場合、検索されたサービスから同じ最短応答時間でサービスをランダムに返すことができます。
  3. すべてのサービスの重みが異なり、応答時間が同じ最短のサービスが 1 つしかない場合は、このサービスを直接返します。
  4. すべてのサービスの重みが同じではなく、同じ最短の応答時間を持つサービスが複数ある場合は、合計の重みに従って乱数が取得され、同じ最短の応答時間を持つ見つかった各サービスの重みから差し引かれます。取得した値が 0 未満の場合、現在のサービスは認定されたサービスです。そのまま戻ります。

おすすめ

転載: blog.csdn.net/qq_22610595/article/details/127816780