Dubbo加權輪詢負載均衡的源碼和Bug,瞭解一下?

​持續輸出原創文章,關注我吧html

本文是對於Dubbo負載均衡策略之一的加權隨機算法的詳細分析。從2.6.4版本聊起,該版本在某些狀況下存在着比較嚴重的性能問題。由問題入手,層層深刻,瞭解該算法在Dubbo中的演變過程,讀懂它的前世此生。git

本文目錄

第一節:什麼是輪詢?github

本小節主要是介紹輪詢算法和其對應的優缺點。引出加權輪詢算法。算法

第二節:什麼是加權輪詢?apache

本小節主要是介紹加權輪詢的機率,並和加權隨機算法作對比。區分二者之間的關係。服務器

第三節:Dubbo 2.6.4版本的實現負載均衡

本小節主要分析了Dubbo 2.6.4版本的源碼,以及對調用過程進行了詳細的分析。並引出該版本的性能問題。dom

第四節:推翻,重建ide

針對Dubbo 2.6.4版本的性能問題,在對應的issue中進行了激烈的討論。並提出了初版優化意見,時間複雜度優化到了常量級。但不久以後,又有人發現了該版本的其餘問題,計算過程不夠平滑。性能

第五節:再推翻,再重建,平滑加權。

針對改進後的算法仍是不夠平滑的問題,最終藉助Nginx的思想,融入了平滑加權的過程,造成最終版。

什麼是輪詢?

在描述加權輪詢以前,先解釋一下什麼是輪詢算法,以下圖所示:

假設咱們有A、B、C三臺服務器,共計處理6個請求,服務處理請求的狀況以下:

第一個請求發送給了A服務器

第二個請求發送給了B服務器

第三個請求發送給了C服務器

第四個請求發送給了A服務器

第五個請求發送給了B服務器

第六個請求發送給了C服務器

......

上面這個例子演示的過程就叫作輪詢。能夠看出,所謂輪詢就是將請求輪流分配給每臺服務器

輪詢的優勢是無需記錄當前全部服務器的連接狀態,因此它一種無狀態負載均衡算法,實現簡單,適用於每臺服務器性能相近的場景下。

輪詢的缺點也是顯而易見的,它的應用場景要求全部服務器的性能都相同,很是的侷限。

大多數實際狀況下,服務器性能是各有差別,針對性能好的服務器,咱們須要讓它承擔更多的請求,即須要給它配上更高的權重。

因此加權輪詢,應運而生。

什麼是加權輪詢?

爲了解決輪詢算法應用場景的侷限性。當遇到每臺服務器的性能不一致的狀況,咱們須要對輪詢過程進行加權,以調控每臺服務器的負載。

通過加權後,每臺服務器可以獲得的請求數比例,**接近或等於他們的權重比。**好比服務器 A、B、C 權重比爲 5:3:2。那麼在10次請求中,服務器 A 將收到其中的5次請求,服務器 B 會收到其中的3次請求,服務器 C 則收到其中的2次請求。

這裏要和加權隨機算法作區分哦。加權隨機我在《一文講透Dubbo負載均衡之最小活躍數算法》中介紹過,直接把畫的圖拿過來:

上面這圖是按照比例畫的,能夠直觀的看到,對於某一個請求,區間(權重)越大的服務器,就越可能會承擔這個請求。因此,當請求足夠多的時候,各個服務器承擔的請求數,應該就是區間,即權重的比值。

假設有A、B、C三臺服務器,權重之比爲5:3:2,一共處理10個請求。

那麼負載均衡採用加權隨機算法時,頗有可能A、B服務就處理完了這10個請求,由於它是隨機調用。

負載均衡採用輪詢加權算法時,A、B、C服務必定是分別承擔五、三、2個請求。

Dubbo2.6.4版本的實現

對於Dubbo2.6.4版本的實現分析,能夠看下圖,我加了不少註釋,其中的輸出語句都是我加的:

示例代碼仍是沿用以前文章中的Demo,不瞭解的能夠查看《一文講透Dubbo負載均衡之最小活躍數算法》,本文分別在2088一、2088二、20883端口啓動三個服務,各自的權重分別爲1,2,3。

客戶端調用8次:

輸出結果以下:

能夠看到第七次調用後mod=0,回到了第一次調用的狀態。造成了一個閉環。

再看看判斷的條件是什麼:

其中mod在代碼中扮演了極其重要的角色,mod根據一個方法的調用次數不一樣而不一樣,取值範圍是[0,weightSum)。

由於weightSum=6,因此列舉mod不一樣值時,最終的選擇結果和權重變化:

能夠看到20881,20882,20883承擔的請求數量比值爲1:2:3。同時咱們能夠看出,當 mod >= 1 後,20881端口的服務就不會被選中了,由於它的權重被減爲0了。當 mod >= 4 後,20882端口的服務就不會被選中了,由於它的權重被減爲0了。

結合判斷條件和輸出結果,咱們詳細分析一下(下面內容稍微有點繞,若是看不懂,多結合上面的圖片看幾回):

  • 第一次調用

mod=0,第一次循環就知足代碼塊①的條件,直接返回當前循環的invoker,即20881端口的服務。此時各端口的權重狀況以下:

  • 第二次調用

mod=1,須要進入代碼塊②,對mod進行一次遞減。

第一次循環對20881端口的服務權重減一,mod-1=0。

第二次循環,mod=0,循環對象是20882端口的服務,權重爲2,知足代碼塊①,返回當前循環的20882端口的服務,此時各端口的權重狀況以下:

  • 第三次調用

mod=2,須要進入代碼塊②,對mod進行兩次遞減。

第一次循環對20881端口的服務權重減一,mod-1=1;

第二次循環對20882端口的服務權重減一,mod-1=0;

第三次循環時,mod已經爲0,當前循環的是20883端口的服務,權重爲3,知足代碼塊①,返回當前循環的20883端口的服務,此時各端口的權重狀況以下:

  • 第四次調用

mod=3,須要進入代碼塊②,對mod進行三次遞減。

第一次循環對20881端口的服務權重減一,從1變爲0,mod-1=2;

第二次循環對20882端口的服務權重減一,從2變爲1,mod-1=1;

第三次循環對20883端口的服務權重減一,從3變爲2,mod-1=0;

第四次循環的是20881端口的服務,此時mod已經爲0,可是20881端口的服務的權重已經變爲0了,不知足代碼塊①和代碼塊②,進入第五次循環。

第五次循環的是20882端口的服務,當前權重爲1,mod=0,知足代碼塊①,返回20882端口的服務,此時各端口的權重狀況以下:

  • 第五次調用

mod=4,須要進入代碼塊②,對mod進行四次遞減。

第一次循環對20881端口的服務權重減一,從1變爲0,mod-1=3;

第二次循環對20882端口的服務權重減一,從2變爲1,mod-1=2;

第三次循環對20883端口的服務權重減一,從3變爲2,mod-1=1;

第四次循環的是20881端口的服務,此時mod爲1,可是20881端口的服務的權重已經變爲0了,不知足代碼塊②,mod不變,進入第五次循環。

第五次循環時,mod爲1,循環對象是20882端口的服務,權重爲1,知足代碼塊②,權重從1變爲0,mod從1變爲0,進入第六次循環。

第六次循環時,mod爲0,循環對象是20883端口的服務,權重爲2,知足條件①,返回當前20883端口的服務,此時各端口的權重狀況以下:

  • 第六次調用

第六次調用,mod=5,會循環九次,最終選擇20883端口的服務,讀者能夠自行分析一波,分析出來了,就瞭解的透透的了。

  • 第七次調用

第七次調用,又回到mod=0的狀態:

2.6.4版本的加權輪詢就分析完了,可是事情並無這麼簡單。這個版本的加權輪詢是有性能問題的。

該問題對應的issue地址以下:

github.com/apache/dubb…

問題出如今invoker返回的時機上:

截取issue裏面的一個回答:

10分鐘才選出一個invoker,還怎麼玩?

有時間能夠讀一讀這個issue,裏面各路大神針對該問題進行了激烈的討論,第一種改造方案被接受後,很快就被推翻,被第二種方案代替,能夠說優化思路十分值得學習,很精彩,接下來的行文路線就是按照該issue展開的。

推翻,重建。

上面的代碼時間複雜度是O(mod),而第一次修復以後時間複雜度下降到了常量級別。能夠說是一次很是優秀的優化,值得咱們學習,看一下優化以後的代碼,我加了不少註釋:

其關鍵優化的點是這段代碼,我加入輸出語句,便於分析。

輸出日誌以下:

把上面的輸出轉化到表格中去,7次請求的選擇過程以下:

該算法的原理是:

把服務端都放到集合中(invokerToWeightList),而後獲取服務端個數(length),並計算出服務端權重最大的值(maxWeight)。

index表示本次請求到來時,處理該請求的服務端下標,初始值爲0,取值範圍是[0,length)。

currentWeight表示當前調度的權重,初始值爲0,取值範圍是[0,maxWeight)。

當請求到來時,從index(就是0)開始輪詢服務端集合(invokerToWeightList),若是是一輪循環的開始(index=0)時,則對currentWeight進行加一操做(不會超過maxWeight),在循環中找出第一個權重大於currentWeight的服務並返回。

這裏說的一輪循環是指index再次變爲0所經歷過的循環,這裏能夠把index=0看作是一輪循環的開始。每一輪循環的次數與Invoker的數量有關,Invoker數量一般不會太多,因此咱們能夠認爲上面代碼的時間複雜度爲常數級。

從issue上看出,這個算法最終被merged了。

可是很快又被推翻了:

**這個算法不夠平滑。**什麼意思呢?

翻譯一下上面的內容就是:服務器[A, B, C]對應權重[5, 1, 1]。進行7次負載均衡後,選擇出來的序列爲[A, A, A, A, A, B, C]。前5個請求所有都落在了服務器A上,這將會使服務器A短期內接收大量的請求,壓力陡增。而B和C此時無請求,處於空閒狀態。而咱們指望的結果是這樣的[A, A, B, A, C, A, A],不一樣服務器能夠穿插獲取請求。

咱們設置20881端口的權重爲5,2088二、20883端口的權重均爲1。

進行實驗,發現確實如此:能夠看到一共進行7次請求,第1次到5次請求都分發給了權重爲5的20881端口的服務,前五次請求,20881和20882都處於空閒狀態:

轉化爲表格以下:

從表格的最終結果一欄也能夠直觀的看出,七次請求對應的服務器端口爲:

分佈確實不夠均勻。

再推翻,再重建,平滑加權。

從issue中能夠看到,再次重構的加權算法的靈感來源是Nginx的平滑加權輪詢負載均衡:

看代碼以前,先介紹其計算過程。

假設每一個服務器有兩個權重,一個是配置的weight,不會變化,一個是currentWeight會動態調整,初始值爲0。當有新的請求進來時,遍歷服務器列表,讓它的currentWeight加上自身權重。遍歷完成後,找到最大的currentWeight,並將其減去權重總和,而後返回相應的服務器便可。

若是你仍是不知道上面的表格是如何算出來的,我再給你詳細的分析一下第一、2個請求的計算過程:

第一個請求計算過程以下:

第二個請求計算過程以下:

後面的請求你就能夠本身分析了。

從表格的最終結果一欄也能夠直觀的看出,七次請求對應的服務器端口爲:

能夠看到,權重之比一樣是5:1:1,可是最終的請求分發的就比較的"平滑"。對比一下:

對於平滑加權算法,我想多說一句。我以爲這個算法很是的神奇,我是完全的明白了它每一步的計算過程,知道它最終會造成一個閉環,可是我想了好久,我仍是不知道背後的數學原理是什麼,不明白爲何會造成一個閉環,很是的神奇。

可是咱們只要可以理解我前面所表達的平滑加權輪詢算法的計算過程,知道其最終會造成閉環,就能理解下面的代碼。配合代碼中的註釋食用,效果更佳。如下代碼以及註釋來源官網:

dubbo.apache.org/zh-cn/docs/…

最後說一句

Dubbo官方提供了四種負載均衡算法,分別是:

ConsistentHashLoadBalance 一致性哈希算法

LeastActiveLoadBalance 最小活躍數算法

RandomLoadBalance 加權隨機算法

RoundRobinLoadBalance 加權輪詢算法

對於官方提供的加權隨機算法,原理十分簡單。因此在《一文講透Dubbo負載均衡之最小活躍數算法》中也提到過。

本文是Dubbo負載均衡算法的最後一篇。前兩篇爲:

《一文講透Dubbo負載均衡之最小活躍數算法》

《Dubbo一致性哈希負載均衡的源碼和Bug,瞭解一下?》

至此,Dubbo的負載均衡算法都已分享完成。

才疏學淺,不免會有紕漏,若是你發現了錯誤的地方,還請你留言給我指出來,我對其加以修改。

若是你以爲文章還不錯,你的轉發、分享、讚揚、點贊、留言就是對我最大的鼓勵。

感謝您的閱讀**,**十分歡迎並感謝您的關注。

以上。

原創不易,求個關注。

公衆號-why技術
相關文章
相關標籤/搜索