【Dubbo】負載均衡

一. 概述

版本:2.7.8

解決問題

  • 當有多個服務提供者時,避免請求集中到其中一個或多個,導致負載過高,服務不可用,需要做一定的負載均衡策略。
  • Dubbo提供了多種均衡策略,默認爲random,也就是每次加權隨機調用一臺服務提供者的服務。

二. Dubbo負載均衡模式

Random LoadBalance:加權隨機

  • 按照概率設置權重,比較均勻,並且可以動態調節提供者的權重。

RoundRobin LoadBalance:輪詢

  • 輪詢,按公約後的權重設置輪詢比率。會存在執行比較慢的服務提供者堆積請求的情況,比如一個機器執行得非常慢,但是機器沒有宕機(如果宕機了,那麼當前機器會從ZooKeeper 的服務列表中刪除)。
  • 當很多新的請求到達該機器後,由於之前的請求還沒處理完,會導致新的請求被堆積,久而久之,消費者調用這臺機器上的所有請求都會被阻塞。

LeastActive LoadBalance:最少活躍調用數

  • 如果每個提供者的活躍數相同,則隨機選擇一個。
  • 在每個服務提供者裏維護着一個活躍數計數器,用來記錄當前同時處理請求的個數,也就是併發處理任務的個數。這個值越小,說明當前服務提供者處理的速度越快或者當前機器的負載比較低,所以路由選擇時就選擇該活躍度最小的機器。
  • 如果一個服務提供者處理速度很慢,由於堆積,同時處理的請求就比較多,也就是說活躍調用數較大,處理速度慢。這時,處理速度慢的提供者將收到更少的請求。

ConsistentHash LoadBalance一致性Hash策略

  • 一致性Hash,可以保證相同參數的請求總是發到同一提供者,當某一臺提供者機器宕機時,原本發往該提供者的請求,將基於虛擬節點平攤給其他提供者,這樣就不會引起劇烈變動。

三. LoadBalance接口及實現類結構圖

在這裏插入圖片描述


四. 源碼解析

1. 接口LoadBalance

  • 負載均衡模式默認爲:random
package org.apache.dubbo.rpc.cluster;

@SPI(RandomLoadBalance.NAME)
public interface LoadBalance {

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

}

2. AbstractLoadBalance抽象類

  • 使用模板模式執行公共業務
  • 各個實現類實現抽象方法(doSelect)
  • getWeight:從URL中獲取各個服務提供者的權重
package org.apache.dubbo.rpc.cluster.loadbalance;

public abstract class AbstractLoadBalance implements LoadBalance {

   @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
     */
    int getWeight(Invoker<?> invoker, Invocation invocation) {

        int weight;
        URL url = invoker.getUrl();
        // Multiple registry scenario, load balance among multiple registries.
        if (REGISTRY_SERVICE_REFERENCE_PATH.equals(url.getServiceInterface())) {
            weight = url.getParameter(REGISTRY_KEY + "." + 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);
    }
}

3. RandomLoadBalance加權隨機算法

RandomLoadBalance實現類


package org.apache.dubbo.rpc.cluster.loadbalance;

public class RandomLoadBalance extends AbstractLoadBalance {

    public static final String NAME = "random";

    /**
     * 加權隨機從invokers中選擇一個
     * @param invokers invokers集合
     * @param url URL
     * @param invocation Invocation
     * @param <T>
     * @return 選擇的invoker
     */
    @Override
    protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {

        // Number of invokers
        int length = invokers.size();
        
        // Every invoker has the same weight?
        boolean sameWeight = true;
        
        // the weight of every invokers
        int[] weights = new int[length];
        
        // the first invoker's weight
        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) {
        
            // If (not every invoker has the same weight & at least one invoker's weight>0), select randomly based on totalWeight.
            // 使用 ThreadLocalRandom.current().nextInt(length)從所有服務提供者裏隨機選擇一個服務提供者進行調用。需要注意的是,這裏沒有使用Random而是使用了ThreadLocalRandom,這是出於性能上的考慮,因 爲Random在高併發下會導致大量線程競爭同一個原子變量,導致大量線程原地自旋,從而浪費CPU資源
            int offset = ThreadLocalRandom.current().nextInt(totalWeight);
            
            // Return a invoker based on the random value.
            for (int i = 0; i < length; i++) {
                offset -= weights[i];
                if (offset < 0) {
                    return invokers.get(i);
                }
            }
        }
        // If all invokers have the same weight value or totalWeight=0, return evenly.
        return invokers.get(ThreadLocalRandom.current().nextInt(length));
    }
}

4. 其它模式(參考源碼)


五. 實現自定義LoadBalance

1. 消費端使用

2. 功能:自定義負載均衡實現

3. 定義實現類

MyLoadBalance實現類

package org.apache.dubbo.rpc.cluster.loadbalance;

public class MyLoadBalance extends AbstractLoadBalance {

    @Override
    protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {

        // 自定義複雜均衡算法,從invokers中選擇一個返回
    }
}

引入SPI

  • resources新增文件夾META-INF.dubbo
  • 新建文件:org.apache.dubbo.rpc.cluster.LoadBalance
  • 文件內容:myLoadBalance=org.apache.dubbo.rpc.cluster.loadbalance.MyLoadBalance

六. 使用自定義LoadBalance

public static void main(String[] args) {

        // 1.創建服務引用對象實例
        ReferenceConfig<GreetingService> referenceConfig = new ReferenceConfig<>();

        // 2.設置應用程序信息
        referenceConfig.setApplication(new ApplicationConfig("dubbo-consumer"));

        // 3.設置服務註冊中心
        referenceConfig.setRegistry(new RegistryConfig("ZKAddress"));

        // 4.設置服務接口和超時時間
        referenceConfig.setInterface(GreetingService.class);
        referenceConfig.setTimeout(5000);

        // 5.設置自定義負載均衡
        referenceConfig.setLoadbalance("myLoadBalance");

        // 6.設置服務分組與版本
        referenceConfig.setVersion("1.0.0");
        referenceConfig.setGroup("dubbo");

        // 7.引用服務
        GreetingService greetingService = referenceConfig.get();
        
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章