負載均衡-粘性分配與平滑加權輪詢

說到負載均衡,我個人習慣分爲兩種,一種是靜態負載均衡,也就是把確定好的、有限的負載按照一定規則分配到不同的工作單元上,這種負載均衡算法只需要計算一次最終分配狀態即可。一種是動態負載均衡,即每次把新到來的負載按照一定規則分配到不同的工作單元上,這種負載均衡算法需要實時動態計算,並且大部分需要保留上一次分配的狀態。

比如說,kafka中一個topic有多個consumer,那每個consumer需要消費哪些partition都是提前計算好的。這裏負載就是這個topic的所有partition,是一個確定好的、有限的負載。每個consumer就是不同的工作單元。負載均衡算法只需要根據partition數量與consumer數量計算一次,計算出每個consumer消費哪些partition即可。這種就屬於靜態負載均衡。

再比如說,RocketMQ中,producer生產一條消息,需要放到哪個consumeQueue中,是需要實時計算的,並且要根據上一次的結果,才能計算出下一次分配的結果。這裏每一條消息就是負載,consumeQueue就是工作單元,我們需要知道上一次的分配結果,並且每一個負載的分配都需要動態計算。這種就屬於動態負載均衡。

以上純是個人的一些理解,如有誤導,請及時指正。

再說負載均衡的目的,負載均衡是把負載分攤到多個操作單元上進行執行,從而共同完成任務。是一種解決單點、高併發、水平擴展的方案。所以我們有時候不需要爲了負載均衡而做負載均衡,而是應該先有單點、高併發、水平擴展等需求的時候,再做負載均衡,不能本末倒置。

比如,RocketMQ中發送消息的負載均衡,首先是因爲多個消費端同時消費時,如果都從一個隊列中取數據會有數據競爭的問題,如果進行加鎖又會有性能問題,爲了解決性能問題,提高消費能力,所以把消息索引分到多個隊列中存儲。因爲消息分配到多個隊列中,所以纔有了消息的負載均衡,RocketMQ採用輪詢的方式,把消息按順序依次保存到不同的隊列中。

再比如Kafka中某個主題的某個partition的master屬於哪個broker,是因爲只有master才能對外提供服務,如果所有master都集中在一個broker上,會導致broker的負載不均衡,導致性能降低。所以要對master進行負載均衡。

以上這兩個負載均衡的案例都是利用水平擴展的方式,提高服務的性能,同時也自然而然的帶來了負載均衡的問題。

所以,可以說是因爲先有了性能、單點等問題,所以要對工作單元水平擴展。有了多個工作單元,才需要考慮負載均衡的問題。

常見的負載均衡算法有很多:

按照我的分類分爲,靜態算法:輪詢分配、範圍分配、粘性分配、一致性hash、hash等等。

動態算法:輪詢、加權、最快響應、平滑加權輪詢等。

這裏比較有意思的兩種分配算法,一個是靜態算法裏的粘性分配,一個是動態算法裏的平滑加權輪詢。

首先粘性分配,是指當工作單元發生變化時,負載分配結果的變化最小。其實一致性hash算法就是爲了解決這個問題。但是,當工作單元與負載數量都很少的情況下,一致性hash算法的分配結果很有很大程度的不均衡現象。

所以又單獨提出來粘性分配,這是一種需要依賴前一個狀態的分配算法,例如kafka中StickyAssignor算法。

他的大概思想就是,先把工作單元與負載按一定的順序排序,然後按照range或者round的方式進行初始分配。並保存分配結果。當工作單元發生變化時,則按照負載算法計算出變化後每個工作單元應該分配的負載數量,如果實際分配的負載比計算後的結果多,則從已分配的負載中按順序取出排序最大的負載加入到待分配負載集合中。如果實際分配的負載比計算後的結果少,則從待分配負載集合中按順序取出排序最小的負載分配給該工作單元。並保存最終分配結果。

基本流程:

  • 工作單元與負載按順序排序。
  • 如果上一次分配狀態爲空,即初始分配,直接按range或round方式分配,並保存分配結果。結束。
  • 如果上一次分配狀態不爲空,如果負載數量增加,則把新增加的負載直接加入到待分配集合中;如果負載數量減少,則直接從上一次分配中踢出縮減的負載。
  • 根據一定的負載算法計算出工作單元變化後,每個工作單元應該分配的負載數量。
  • 如果上一次分配負載數量大於計算後結果,則按照負載排序結果,按順序取出負載加入到待分配集合中。
  • 如果上一次分配負載數量小於計算後結果,則從待分配集合中按順序取出負載分配到給該工作單元。
  • 保存分配結果。

例如,有序號0到9的負載,序號0、1的工作單元。

初始分配結果:0號工作單元對應負載0到4,1號工作單元對應負載5到9。

當新增序號2的工作單元后,計算每個工作單元應分配的負載數量爲:0號3個,1號3個,2號4個。

則遍歷0號工作單元后,0號分配負載:0、1、2,待分配負載集合:3、4。

遍歷1號工作單元后,1號分配負載:5、6、7,待分配負載集合:3、4、8、9

遍歷2號工作單元后,2號分配負載:3、4、8、9,待分配集合空。

 

平滑加權輪詢是加權輪詢的優化算法,首先加權輪詢算法會有流量集中分配的問題,即比如3個工作單元A、B、C的權重分別爲{5、2、3}。

則負載的分配結果爲:

A-A-A-A-A-B-B-C-C-C

平滑加權輪詢就是爲了解決這個問題。

它的基本算法步驟是:

  • 初始每個工作單元 i 的權重爲Wi,並計算所有權重總和Wsum。
  • 將權重最大的工作單元 k 權重Wk,減去權重總和Wsum。
  • 工作單元 k 作爲計算結果。
  • 再把每個工作單元 i 的權重加上最原始分配的權重Wi。
  • 重複以上步驟。

例如還是A、B、C三個工作單元的權重分別分{5、2、3}。

權重 結果 分配後權重
5、2、3 A -5、2、3
0、4、6 C 0、4、-4
5、6、-1 B 5、-4、-1
10、-2、2 A

0、-2、2

5、0、5 A -5、0、5
0、2、8 C 0、2、-2
5、4、1 A -5、4、1
0、6、4 B 0、-4、4
5、-2、7 C 5、-2、-3
10、0、0 A 0、0、0

通過表格可以看出來,計算結果分佈比較均勻,並且保證了按權重分配。並且每一輪後,權重都會重新恢復到最原始狀態,保證後續操作都是重複的。

我們在通過算法證明一下調度算法的合理性。

首先是證明第 i 個工作單元,在一輪中最多被分配Wi次。

記第 i 個工作單元的權重爲Wi,W1+W2+......+Wn=S,即總權重爲S,需要證明在S次分配中,第 i 個工作單元最多被分配Wi次。

假設在S次分配中,已經分配了t次,其中第 i 個工作單元已經被分配了Wi次,那麼 i 的當前權重爲Wci = t * Wi - S * Wi + Wi = (t-S+1)Wi。

因爲t <= S-1,所以Wci <= 0,又因爲Wc1+Wc2+......+Wcn = S > 0,所以一定有一個工作單元 k 的權重大於0,將選擇工作單元k。

並且需要連續S-t次不被選擇後,Wci才能大於等於0,纔有機會被選中。即剩餘的S-t次內,工作單元 i 的權重Wci <= 0。

綜上,在每輪中S次選擇中,工作單元 i 做多被選中Wi次。又因爲S=W1+W2+......+Wn。所以,工作單元 i 只能被選中Wi次。

再證明工作單元 i 不能被連續選擇Wi次。

我們只需要證明,如果工作單元 i 被連續選擇Wi-1次後,下一次一定不會被選中即可。

即工作單元 i 被連續選中Wi-1次後,

Wci = (Wi-1)*Wi - S*(Wi-1)+Wi =(Wi-S)*(Wi-1)+Wi <= -1*(Wi-1)+Wi = 1

即工作單元 i 被連續選擇Wi-1次後,權重小於等於1。

又因爲一定存在另外一個工作單元 j 的權重爲:(Wi-1)*Wj>1。所以下一次一定不會再選擇工作單元i。

所以平滑加權輪詢算法是一個有效算法。

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