聊一聊Pulsar負載均衡原理及優(yōu)化
前言
前段時間我們在升級 Pulsar 版本的時候發(fā)現(xiàn)升級后最后一個節(jié)點始終沒有流量。
雖然對業(yè)務(wù)使用沒有任何影響,但負載不均會導(dǎo)致資源的浪費。
和同事溝通后得知之前的升級也會出現(xiàn)這樣的情況,最終還是人工調(diào)用 Pulsar 的 admin API 完成的負載均衡。
這個問題我嘗試在 Google 和 Pulsar 社區(qū)都沒有找到類似的,不知道是大家都沒碰到還是很少升級集群。
我之前所在的公司就是一個版本走到黑
Pulsar 負載均衡原理
當(dāng)一個集群可以水平擴展后負載均衡就顯得非常重要,根本目的是為了讓每個提供服務(wù)的節(jié)點都能均勻的處理請求,不然擴容就沒有意義了。
在分析這個問題的原因之前我們先看看 Pulsar 負載均衡的實現(xiàn)方案。
我們可以通過這個 broker 的這個配置來控制負載均衡器的開關(guān),默認是打開。
但具體使用哪個實現(xiàn)類來作為負載均衡器也可以在配置文件中指定:
默認使用的是 ModularLoadManagerImpl。
當(dāng) broker 啟動時會從配置文件中讀取配置進行加載,如果加載失敗會使用 SimpleLoadManagerImpl 作為兜底策略。
當(dāng) broker 是一個集群時,只有 leader 節(jié)點的 broker 才會執(zhí)行負載均衡器的邏輯。
Leader 選舉是通過 Zookeeper 實現(xiàn)的。
默然情況下成為 Leader 節(jié)點的 broker 會每分鐘讀取各個 broker 的數(shù)據(jù)來判斷是否有節(jié)點負載過高需要做重平衡。
而是否重平衡的判斷依據(jù)是由 org.apache.pulsar.broker.loadbalance.LoadSheddingStrategy 接口提供的,它其實只有一個函數(shù):
根據(jù)所有 broker 的負載信息計算出一個需要被 unload 的 broker 以及 bundle。
這里解釋下 unload 和 bundle 的概念:
- bundle 是一批 topic 的抽象,將 bundle 和 broker 進行關(guān)聯(lián)后客戶端才能知道應(yīng)當(dāng)連接哪個 broker;而不是直接將 topic 與 broker 綁定,這樣才能實現(xiàn)海量 topic 的管理。
- unload 則是將已經(jīng)與 broker 綁定的 bundle 手動解綁,從而觸發(fā)負載均衡器選擇一臺合適的 broker 重新進行綁定;通常是整個集群負載不均的時候觸發(fā)。
ThresholdShedder 原理
LoadSheddingStrategy 接口目前有三個實現(xiàn),這里以官方默認的 ThresholdShedder 為例:
它的實現(xiàn)算法是根據(jù)帶寬、內(nèi)存、流量等各個指標(biāo)的權(quán)重算出每個節(jié)點的負載值,之后為整個集群計算出一個平均負載值。
當(dāng)集群中有某個節(jié)點的負載值超過平均負載值達到一定程度(可配置的閾值)時,就會觸發(fā) unload,以上圖為例就會將最左邊節(jié)點中紅色部分的 bundle 卸載掉,然后再重新計算一個合適的 broker 進行綁定。
閾值存在的目的是為了避免頻繁的 unload,從而影響客戶端的連接。
問題原因
當(dāng)某些 topic 的流量突然爆增的時候這種負載策略確實可以處理的很好,但在我們集群升級的情況就不一定了。
假設(shè)我這里有三個節(jié)點:
- broker0
- broker1
- broker2
集群升級時會從 broker2->0 進行鏡像替換重啟,假設(shè)在升級前每個 broker 的負載值都是 10。
- 重啟 broker2 時,它所綁定的 bundle 被 broker0/1 接管。
- 升級 broker1 時,它所綁定的 bundle 又被 broker0/2 接管。
- 最后升級 broker0, 它所綁定的 bundle 會被broker1/2 接管。
只要在這之后沒有發(fā)生流量激增到觸發(fā)負載的閾值,那么當(dāng)前的負載情況就會一直保留下去,也就是 broker0 會一直沒有流量。
經(jīng)過我反復(fù)測試,現(xiàn)象也確實如此。
通過這個工具也可以查看各個節(jié)點的負載情況
優(yōu)化方案
這種場景是當(dāng)前 ThresholdShedder 所沒有考慮到的,于是我在我們所使用的版本 2.10.3 的基礎(chǔ)上做了簡單的優(yōu)化:
- 當(dāng)原有邏輯走完之后也沒有獲取需要需要卸載的 bundle,同時也存在一個負載極低的 broker 時(emptyBundle),再觸發(fā)一次 bundle 查詢。
- 按照 broker 所綁定的數(shù)量排序,選擇一個數(shù)量最多的 broker 的 第一個 bundle 進行卸載。
修改后打包發(fā)布,再走一遍升級流程后整個集群負載就是均衡的了。
但其實這個方案并不嚴(yán)謹,第二步選擇的重點是篩選出負載最高的集群中負載最高的 bundle;這里只是簡單的根據(jù)數(shù)量來判斷,并不夠準(zhǔn)確。
正當(dāng)我準(zhǔn)備持續(xù)優(yōu)化時,鬼使神差的我想看看 master 上有人修復(fù)這個問題沒,結(jié)果一看還真有人修復(fù)了;只是還沒正式發(fā)版。
??https://github.com/apache/pulsar/pull/17456??
整體思路是類似的,只是篩選負載需要卸載 bundle 時是根據(jù) bundle 自身的流量來的,這樣會更加精準(zhǔn)。
總結(jié)
不過看社區(qū)的進度等這個優(yōu)化最終能用還不知道得多久,于是我們就自己參考這個思路在管理臺做了類似的功能,當(dāng)升級后出現(xiàn)負載不均衡時人工觸發(fā)一個邏輯:
- 系統(tǒng)根據(jù)各個節(jié)點的負載情況計算出一個負載最高的節(jié)點和 bundle 在頁面上展示。
- 人工二次確認是否要卸載,確認無誤后進行卸載。
本質(zhì)上只是將上述優(yōu)化的自動負載流程改為人工處理了,經(jīng)過測試效果是一樣的。
Pulsar 整個項目其實非常龐大,有著幾十上百個模塊,哪怕每次我只改動一行代碼準(zhǔn)備發(fā)布測試時都得經(jīng)過漫長的編譯+ Docker鏡像打包+上傳私服這些流程,通常需要1~2個小時;但總的來說收獲還是很大的,最近也在提一些 issue 和 PR,希望后面能更深入的參與進社區(qū)。