Dubbo 負載均衡LoadBalance

轉載 http://dubbo.apache.org/zh-cn/docs/source_code_guide/loadbalance.html

簡介

Dubbo 提供了4種負載均衡實現,分別是基於權重隨機算法的 RandomLoadBalance、基於最少活躍調用數算法的LeastActiveLoadBalance、基於 hash 一致性的ConsistentHashLoadBalance,以及基於加權輪詢算法的 RoundRobinLoadBalance

源碼

在 Dubbo 中,所有負載均衡實現類均繼承自 org.apache.dubbo.rpc.cluster.loadbalance.AbstractLoadBalance,該類實現了 LoadBalance 接口,並封裝了一些公共的邏輯

核心的select方法

    @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);

具體的doSelect方法則由子類完成。
在AbstractLoadBalance 中,另一個核心的方法就是getWeight

    /**
     * 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 = invoker.getUrl().getMethodParameter(invocation.getMethodName(), WEIGHT_KEY, DEFAULT_WEIGHT);
        if (weight > 0) {
            long timestamp = invoker.getUrl().getParameter(REMOTE_TIMESTAMP_KEY, 0L);
            if (timestamp > 0L) {
                int uptime = (int) (System.currentTimeMillis() - timestamp);
                int warmup = invoker.getUrl().getParameter(WARMUP_KEY, DEFAULT_WARMUP);
                if (uptime > 0 && uptime < warmup) {
                    weight = calculateWarmupWeight(uptime, warmup, weight);
                }
            }
        }
        return weight >= 0 ? weight : 0;
    }
    /**
     * 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) {
        int ww = (int) ((float) uptime / ((float) warmup / (float) weight));
        return ww < 1 ? 1 : (ww > weight ? weight : ww);
    }

RandomLoadBalance

RandomLoadBalance 是加權隨機算法的具體實現,它的算法思想很簡單。假設我們有一組服務器 servers = [A, B, C],他們對應的權重爲 weights = [5, 3, 2],權重總和爲10。現在把這些權重值平鋪在一維座標值上,[0, 5) 區間屬於服務器 A,[5, 8) 區間屬於服務器 B,[8, 10) 區間屬於服務器 C。接下來通過隨機數生成器生成一個範圍在 [0, 10) 之間的隨機數,然後計算這個隨機數會落到哪個區間上。比如數字3會落到服務器 A 對應的區間上,此時返回服務器 A 即可。權重越大的機器,在座標軸上對應的區間範圍就越大,因此隨機數生成器生成的數字就會有更大的概率落到此區間內。只要隨機數生成器產生的隨機數分佈性很好,在經過多次選擇後,每個服務器被選中的次數比例接近其權重比例。比如,經過一萬次選擇後,服務器 A 被選中的次數大約爲5000次,服務器 B 被選中的次數約爲3000次,服務器 C 被選中的次數約爲2000次。

LeastActiveLoadBalance

LeastActiveLoadBalance 翻譯過來是最小活躍數負載均衡。活躍調用數越小,表明該服務提供者效率越高,單位時間內可處理更多的請求。此時應優先將請求分配給該服務提供者。在具體實現中,每個服務提供者對應一個活躍數 active。初始情況下,所有服務提供者活躍數均爲0。每收到一個請求,活躍數加1,完成請求後則將活躍數減1。在服務運行一段時間後,性能好的服務提供者處理請求的速度更快,因此活躍數下降的也越快,此時這樣的服務提供者能夠優先獲取到新的服務請求、這就是最小活躍數負載均衡算法的基本思想。除了最小活躍數,LeastActiveLoadBalance 在實現上還引入了權重值。所以準確的來說,LeastActiveLoadBalance 是基於加權最小活躍數算法實現的。舉個例子說明一下,在一個服務提供者集羣中,有兩個性能優異的服務提供者。某一時刻它們的活躍數相同,此時 Dubbo 會根據它們的權重去分配請求,權重越大,獲取到新請求的概率就越大。如果兩個服務提供者權重相同,此時隨機選擇一個即可

ConsistentHashLoadBalance

一致性 hash 算法由麻省理工學院的 Karger 及其合作者於1997年提出的,算法提出之初是用於大規模緩存系統的負載均衡。它的工作過程是這樣的,首先根據 ip 或者其他的信息爲緩存節點生成一個 hash,並將這個 hash 投射到 [0, 232 - 1] 的圓環上。當有查詢或寫入請求時,則爲緩存項的 key 生成一個 hash 值。然後查找第一個大於或等於該 hash 值的緩存節點,併到這個節點中查詢或寫入緩存項。如果當前節點掛了,則在下一次查詢或寫入緩存時,爲緩存項查找另一個大於其 hash 值的緩存節點即可。大致效果如下圖所示,每個緩存節點在圓環上佔據一個位置。如果緩存項的 key 的 hash 值小於緩存節點 hash 值,則到該緩存節點中存儲或讀取緩存項。比如下面綠色點對應的緩存項將會被存儲到 cache-2 節點中。由於 cache-3 掛了,原本應該存到該節點中的緩存項最終會存儲到 cache-4 節點中。

2020032-3fe87a7f8763483d.jpg
1393FFB032BCB7F4D6DA3506135F32A70.jpg

下面來看看一致性 hash 在 Dubbo 中的應用。我們把上圖的緩存節點替換成 Dubbo 的服務提供者,於是得到了下圖:

2020032-6651989126b8f0e2.jpg
2B424CE341AF701CA41C65CB20FA8D1CC.jpg

這裏相同顏色的節點均屬於同一個服務提供者,比如 Invoker1-1,Invoker1-2,……, Invoker1-160。這樣做的目的是通過引入虛擬節點,讓 Invoker 在圓環上分散開來,避免數據傾斜問題。所謂數據傾斜是指,由於節點不夠分散,導致大量請求落到了同一個節點上,而其他節點只會接收到了少量請求的情況。比如:

2020032-a48765274557a4c6.jpg
3DC3F823D4B67696AAD65D381314DC69F.jpg

如上,由於 Invoker-1 和 Invoker-2 在圓環上分佈不均,導致系統中75%的請求都會落到 Invoker-1 上,只有 25% 的請求會落到 Invoker-2 上。解決這個問題辦法是引入虛擬節點,通過虛擬節點均衡各個節點的請求量。

ConsistentHashSelector 的構造方法執行了一系列的初始化邏輯,比如從配置中獲取虛擬節點數以及參與 hash 計算的參數下標,默認情況下只使用第一個參數進行 hash。需要特別說明的是,ConsistentHashLoadBalance 的負載均衡邏輯只受參數值影響,具有相同參數值的請求將會被分配給同一個服務提供者。ConsistentHashLoadBalance 不 關係權重,因此使用時需要注意一下。

在獲取虛擬節點數和參數下標配置後,接下來要做的事情是計算虛擬節點 hash 值,並將虛擬節點存儲到 TreeMap 中。到此,ConsistentHashSelector 初始化工作就完成了
選擇的過程相對比較簡單了。首先是對參數進行 md5 以及 hash 運算,得到一個 hash 值。然後再拿這個值到 TreeMap 中查找目標 Invoker 即可。

RoundRobinLoadBalance

RoundRobinLoadBalance。在詳細分析源碼前,我們先來了解一下什麼是加權輪詢。這裏從最簡單的輪詢開始講起,所謂輪詢是指將請求輪流分配給每臺服務器。舉個例子,我們有三臺服務器 A、B、C。我們將第一個請求分配給服務器 A,第二個請求分配給服務器 B,第三個請求分配給服務器 C,第四個請求再次分配給服務器 A。這個過程就叫做輪詢。輪詢是一種無狀態負載均衡算法,實現簡單,適用於每臺服務器性能相近的場景下。但現實情況下,我們並不能保證每臺服務器性能均相近。如果我們將等量的請求分配給性能較差的服務器,這顯然是不合理的。因此,這個時候我們需要對輪詢過程進行加權,以調控每臺服務器的負載。經過加權後,每臺服務器能夠得到的請求數比例,接近或等於他們的權重比。比如服務器 A、B、C 權重比爲 5:2:1。那麼在8次請求中,服務器 A 將收到其中的5次請求,服務器 B 會收到其中的2次請求,服務器 C 則收到其中的1次請求。

參考

http://dubbo.apache.org/zh-cn/docs/source_code_guide/loadbalance.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章