天天看點

BIGO優化Apache Pulsar系列-Bookie負載均衡

作者:閃念基因

背景

基于強大的音視訊處理技術、全球音視訊實時傳輸技術、人工智能技術、CDN 技術,BIGO 研發及投資了多款音視訊類社交及内容産品,包括 Bigo Live、Likee等。目前,BIGO 在全球擁有億級月活使用者,産品及服務已覆寫超過 150 個國家和地區。随着業務的迅速增長,BIGO 消息隊列平台承載的資料規模出現了成倍增長,下遊的線上模型訓練、線上推薦、實時資料分析、實時數倉等業務對消息的實時性和穩定性提出了更高的要求。

Apache Pulsar是Apache軟體基金會頂級項目,作為下一代雲原生分布式消息流平台,具有強一緻性、高吞吐以及低延時等特性。Pulsar 采用計算與存儲分離的分層架構設計,存儲層由Apache BookKeeper項目提供支援,一個BookKeeper伺服器節點稱為bookie。

為了實作讀寫隔離,部署過程中會給一個bookie配置設定一個ledger盤和一個journal盤,寫入資料時順序寫入journal盤,成功持久化後即可立刻響應成功,進而實作低延遲的寫入,bookie内部還會聚合屬于同一個Ledger的所有entry,然後刷盤到ledger盤,這樣讀取的時候也能支援順序讀。

可見,journal盤對寫入速度要求較高,對存儲空間大小要求較低;ledger盤對存儲空間大小要求較高,對寫入速度要求較低。理論上journal盤采用SSD能達到更好的寫入性能,ledger盤采用大容量的HDD即可滿足需求。但消息隊列寫入的資料均為熱資料,寫入資料量大,儲存時間不會超過7天,而SSD使用的閃存媒體的可程式設計和擦除次數有限,是以實踐過程中基于成本考慮,并沒有采用SSD磁盤供journal盤使用。我們全部采用大容量的HDD盤,一個bookie獨占一個磁盤作為一個Ledger盤,三個bookie共用一個磁盤作為Journal盤。

注記:盡管多個bookie共用一個磁盤作為journal盤,但是journal磁盤的空間使用率也是極低的,會有存儲空間資源的浪費,我們接下來會将journal盤去掉,釋放更多的存儲空間,供緊缺的ledger盤使用,同時也能進一步緩解寫延遲高的問題。

挑戰

随着業務流量的持續增長,叢集的壓力越來越大,直接表現就是叢集的P99寫延遲升高,一般來說當P99寫延遲達到秒級時,就會觸發電話告警,提醒叢集擴容,緩解單台機器的壓力。

但是,寫延遲過高不一定是叢集硬體資源不夠導緻的,也有可能各種其他原因,比如說單個topic分區的流量過大,達到上百MB,而單個HDD盤的最大順序寫入速度也就100多MB,而且journal盤還是三個bookie共用的,此時該journal盤服務的所有分區肯定都會出現寫延遲高的問題,解決辦法很簡單,擴大topic分區數,将流量打散到多個journal盤即可。

還有可能是系統設計本身的問題:journal盤之間的流量壓力不均衡。我們收集統計journal盤的寫入吞吐統計圖,橫軸為流量吞吐區間,縱軸為處于該流量吞吐壓力區間的journal盤個數,如下圖所示:

BIGO優化Apache Pulsar系列-Bookie負載均衡

Instance: instance4 device:sdw, value: 154.90 MB/s

Instance: instance7 device:sdw, value: 130.20 MB/s

Instance: instance5 device:sdw, value: 124.98 MB/s

Instance: instance1 device:sdt, value: 120.98 MB/s

Instance: instance15 device:sdw, value: 119.92 MB/s

......

Instance: instance10 device:sds, value: 47.44 MB/s

Instance: instance13 device:sds, value: 40.51 MB/s

Instance: instance12 device:sdu, value: 37.99 MB/s

Instance: instance14 device:sds, value: 23.67 MB/s

由此可見,journal盤的寫入吞吐的負載分布相當地不均衡。壓力最大的journal盤吞吐能達到150MB/s,這接近HDD的性能上限了,卻有不少journal盤流量吞吐小于50MB/s。這構成了我們做bookie寫負載均衡的基本動機。

架構設計

目前BookKeeper在建立Ledger的時候,會根據放置政策來選擇bookie,比如說使用RackawareEnsemblePlacementPolicy來強制不同的資料副本放置在不同的機架上,以保證機架級别的容災;或者使用RegionAwareEnsemblePlacementPolicy強制将不同資料的副本放置在屬于不同區域的不同機架上以實作區域級故障容災。在經過這些放置政策的選擇後,還是會有相當多的候選bookie作為新Ledger的ensemble,這就給了我們實作負載均衡算法的空間。

我們隻做普通寫場景的負載均衡,對于讀和recovery寫的場景都不加入負載均衡。原因如下:

·讀的負載均衡

一方面是目前讀延遲問題不嚴重,另一方面是讀ledger時隻能從三副本(甚至是2副本)裡挑選Bookie來讀取,選擇的空間不大,是以不考慮讀方面的負載均衡。

·recovery

auto recovery的時候預設是搶到任務的bookie把資料拷貝到本地,這一塊不建議加入負載均衡機制。

對比Broker負載均衡

目前BookKeeper并沒有一個負載均衡的架構,不像broker已經内嵌了負載均衡器,使用者覺得算法不滿意的可以自行設計、實作新的算法。是以,我們在實作負載均衡之前,得要先建構起來一個新的架構。下面先對比Pulsar broker的負載均衡機制,來指導BookKeeper的負載均衡架構設計。

負載單元

·broker的負載單元是bundle

bundle的生命周期是持久的,或者說它施加的負載可以認為是長久的;broker使用AvgShedder做一次負載均衡決策後,基本都不用再做決策了,因為流量在一定時間内是穩定的。

注記:AvgShedder是BIGO内部實作的一個負載均衡算法,代碼已經貢獻給社群。

https://github.com/apache/pulsar/pull/18186

bundle的負載資訊通過時間聚合的方式統計得到它的長期、短期負載資訊,負載資訊包括寫入/消費消息數率、吞吐量。

·bookie的負載單元是ledger

ledger的生命周期是相對短暫的(但至少為10min,這一點後面分析可知),關閉它之後它就不會再施加任何寫入壓力了;bookie建立ledger是持續進行的事情,需要不斷地做決策。ledger的負載資訊可以包含它的預計空間大小、預計寫入吞吐大小,供決策者使用。

資源機關

·broker的資源機關就是單個機器/節點

因為一台機器隻會部署一個broker,機器上的各種負載資訊(包括CPU使用率、網卡使用率等)可以視為該broker一對一的負載資訊。

·bookie的資源機關應該是磁盤

因為一個journal盤實際上會給多個bookie使用,journal盤主要關心寫入吞吐、寫入延遲的名額,同一個journal盤裡的不同bookie寫入是否均衡其實沒有影響。

對于ledger盤,一個bookie獨享一個ledger盤,ledger盤主要關心它的磁盤空間使用率。

那麼我們在挑選bookie時,就需要同時考慮一個bookie的journal盤寫入吞吐、ledger盤的磁盤使用率,是以可以使用一個二進制組來表示單個Bookie的負載資訊。

決策頻率

·broker基本隻需要在broker加入、退出的時候做一次決策

當然如果負載均衡算法不好,或者配置不合适,可能會發生頻繁的bundle解除安裝、加載的情況,這會造成用戶端流量抖動的問題,這是broker負載均衡算法的問題了,這裡不深入讨論。

·bookie需要不斷做決策

一個topic是由一個ledger清單構成的,當達到門檻值時broker會主動關閉一個Ledger,并建立一個新的Ledger給該topic使用。是以叢集是需要不斷地建立新Ledger的。

決策的頻率是會影響到架構的設計的,具體來講,會影響我們選擇中心化決策還是分布式決策。

— 如果每個broker都自己做決策(分布式決策),由于每個broker拿到同一份資料、執行同一個算法,這種情況下會造成流量傾斜到某幾個bookie,負載均衡效果可能會很差。當然可以在算法層面緩解該問題,即給算法引入随機化因素,盡量緩解傾斜的問題。

— 如果實作中心化的決策,那就就沒有傾斜的問題。

可以看到,叢集的負載均衡決策頻率越高,我們選擇分布式決策的架構的風險會越高。

我們搜集某線上叢集的統計資料,如下圖是某叢集流量高峰時的統計圖,每一個柱子對應1s内建立ledger的個數,可見,就算沒有叢集重新開機平常也是能達到十幾個ledger的建立速度的。

BIGO優化Apache Pulsar系列-Bookie負載均衡

下圖是流量低峰時的建立頻率,稍微低一些。

BIGO優化Apache Pulsar系列-Bookie負載均衡

這種決策頻率相當來說不高,是以我們最終選擇的架構是分布式決策,每個broker都可以做決策。

負載變化速度

·broker一定時間内可以認為流量是不變的

前面也提到過,broker使用AvgShedder做一次負載均衡決策後,基本都不用再做決策了,因為流量一定時間内基本是穩定的,以一天為周期,周期性波動。

·bookie的負載是會一直動态變化的

因為bookie的負載單元是ledger,而ledger是有生命周期的,達到門檻值時就會觸發ledger關閉,建立新的ledger。一個分區在一個時間點上隻有一個ledger服務,是以一個Ledger施加的壓力就是一個分區施加的壓力,一個journal盤少服務或者多服務一個Ledger的差別是很明顯的,甚至可能一個journal盤被某一個分區的流量幾乎完全霸占。

如果bookie的負載變化很快,那麼也是無法做負載均衡的,因為剛收集的負載資料可能都已經過期了。

搜集日志(淩晨截至早上11:00的日志資料),針對每個topic/分區,搜集它兩次建立ledger之間的時間間隔,就得到了一個ledger的生命周期長度,統計其分布得到下圖,x軸為ledger的生命周期長度,y軸為ledger的個數。(下圖隻展示了800s以下的資料,展示太多内容圖檔會很難看。)

BIGO優化Apache Pulsar系列-Bookie負載均衡

注記:因為生命長度為600s的ledger的數目顯著地高于其他時間長度的,是以這裡y軸使用了log坐标軸。

BIGO優化Apache Pulsar系列-Bookie負載均衡

數量這麼多的ledger生命長度為600s,即10min,而且沒有小于600s的,肯定是有原因的。

檢視broker配置managedLedgerMinLedgerRolloverTimeMinutes預設為10min,即一個ledger最快也要10min才能切換到下一個ledger。

當達到managedLedgerMinLedgerRolloverTimeMinutes時間,而且另外三個門檻值有一個觸發時,才會觸發ledger的切換。

BIGO優化Apache Pulsar系列-Bookie負載均衡

是以,一個ledger的生命周期其實不是很短,它對某個bookie的流量壓力至少也要持續10min(不考慮bookie滾動重新開機導緻ledger close的情況),如果覺得bookie流量變化速度還是過快,可以調大managedLedgerMinLedgerRolloverTimeMinutes配置為15min。

當然,雖然Ledger的生命周期是決定journal盤寫入壓力是否穩定的一個核心因素,但我們還是要看一下journal盤寫入吞吐的真實波動情況,因為一個journal盤不隻服務一個Ledger,真實環境中多個Ledger動态變化的複雜系統裡,journal盤寫入吞吐的變化周期并不等于一個Ledger的生命周期。

如下圖:

BIGO優化Apache Pulsar系列-Bookie負載均衡

由于Prometheus拉起監控資料的時間間隔為15s,是以這裡也隻能簡單估測一分鐘内的資料都還是相對準确的。是以,給bookie做負載均衡還是具有可行性的。

負載資訊上報方式、存儲位置

broker

broker的負載資訊由每個broker上報到zk上。但是由于每個broker、bundle都對應一個znode,特别是bundle個數達到1k多,而且目前每個broker都會去讀取所有資訊,導緻每次拉取資料時對zk造成較大壓力,目前通過降低上報、拉取的頻率來大大緩解了(每3min一次)。

broker最新版的負載均衡器已經把負載資料遷移到topic裡存儲了,負載均衡本身不是核心子產品,不能濫用zk的資源,zk延遲拉高時叢集會面臨整個挂掉的風險。

bookie

bookie是自己上報資料呢?還是由外部元件拉取呢?上報的話寫給誰呢?zk還是broker?

前面說了,負載資料隻有磁盤的使用率、寫入吞吐/延遲,搜集的邏輯都比較簡單,也不一定要bookie來參與,其實我們隻需要知道所有磁盤的資訊,然後知道磁盤與bookie之間的映射關系就足夠了,是以選擇餘地很大。

下面列舉一些可能的方式供參考:

1. 最傳統的方式,bookie上報到zk上。

叢集有幾百個bookie,是以至少需要幾百個znode來存儲該資料,一方面是頻繁更新資料會對zk造成壓力,而且broker、bookie叢集都強依賴zk,zk一旦出了問題整個叢集都會挂,是以會引入新的風險;而且zk的讀延遲是會達到秒級的(特别是replicate的時候,這種延遲對于bookie的負載均衡可能是無法接受的),是以這種方案其實可以不用考慮了。

2. 每個bookie把自身的負載資料寫入一個對應的ledger,決策者去讀這些ledger。

注記:因為一個ledger隻能有一個writer,是以不能多個bookie同時往一個ledger裡寫,每個bookie都往自己的ledger寫。

優點:能利用到bookie自身優秀的架構設計,延遲能達到幾毫秒;而且對于多個決策者的情況下,多個決策者都去讀這個ledger就行了,能很好地利用到讀寫緩存。

憂慮:每個bookie都擁有一個自己的ledger,是以會增加幾百個Ledger用于寫入負載資訊,可能會增加叢集中繼資料壓力。

3. 每個bookie僅在記憶體維護自身負載資料,提供接口給外部通路。

優點:負載資料不用使用存儲起來,因為負載資料變化比較快,參考後面的負載變化速度圖,是以存儲起來反而必要性不大。

憂慮:暴露接口給外部調用,較高請求頻率下是否延遲會很高?比如說分布式決策的情況下,每個broker都需要給每個bookie都發送請求,當叢集規模較大時則可能會遇到請求壓力大和延遲高的問題。

為了叢集的架構簡潔,而且我們的叢集規模不算很大,我們最終采用這種方式來實作。

4. 由一個外部的元件來抓取叢集所有機器的磁盤的負載情況,結合zk上的資訊映射好磁盤和bookie的關系,該元件提供接口給外部通路。

優點:

-壓力、資源消耗最小,該元件隻需對每台機器都搜集一下磁盤負載即可,每個broker隻需要對該元件請求一次即可,是以可能延遲也能達到一個理想的效果。

-實作起來也最為容易。

缺點:

-增加叢集的元件,架構變複雜。

-如果要采集bookie内部的名額,則外部元件難以搜集到這種資料,但是目前應該是不考慮bookie内部的一些名額的,名額夠用就行,過多不僅容易導緻邏輯複雜,而且容易造成效果差的問題,Pulsar負載均衡考慮直接記憶體使用率就是一個反面例子,Pulsar新版代碼已經預設關掉考慮直接記憶體了。

https://github.com/apache/pulsar/pull/21168

決策者

broker

broker的決策者就是leader broker。它收集所有broker、所有bundle的負載資訊,然後做中心化決策,而且因為broker隻需要做一次決策,是以它一次性做出最優的決策,後續需要多次觸發門檻值AvgShedder才會再觸發負載均衡,避免錯誤決策。

bookie

bookie的決策者可以放在bookie client裡實作,也可以引入一個外部的元件advisor角色,下面讨論兩種設計的優缺點:

1. bookie client裡實作

優點:不需要引入外部的元件,避免增加系統的複雜度。

缺點:

- 每個broker都做決策,會出現前面提到的流量傾斜的問題;

- 而且每個broker都要讀流量,會造成負載資料存儲端的壓力,特别是用zk來存儲的時候。

- 侵入bookie client的代碼。

- 需要增加資源來頻繁地拉取更新負載資料,否則負載均衡效果差。

2. 引入advisor角色

優點:

- 它無需高可用,隻是一個盡力而為的服務,給bookie client提供建議,幫忙挑選ensemble,如果它挂了或者逾時了,bookie client直接使用原來的邏輯兜底即可。

- 對bookie client的代碼僅做細微的修改,而且基本沒有風險。

- 同一個資料中心裡資料包往返的時間隻有幾十us,延遲可以接受。

- 中心化決策,避免流量傾斜的問題,且同一份負載資料隻需讀取一次。

缺點:增加一個元件,運維麻煩。

我們最終選擇了在bookie client裡實作。

架構設計

根據前面的所有分析與權衡,我們最終采用的架構如下:bookie 新開一個線程采集資料,并通過接口提供給外部,然後broker也新開一個線程,通過通路接口的方式不斷搜集資料(如每3s或者5s擷取一次),并在每次建立ledger時,本地執行負載均衡算法做決策。

BIGO優化Apache Pulsar系列-Bookie負載均衡

上圖介紹了Broker、Bookie負載資訊搜集的架構,以及Broker利用這些負載資訊本地執行負載均衡算法。

BIGO優化Apache Pulsar系列-Bookie負載均衡

上圖具體介紹了建立一個ledger的流程。

在請求bookie client建立Ledger時,可以提供一些資料,即ledger的預計空間大小、預計寫入吞吐大小。因為我們隻考慮broker寫入端(不考慮auto recovery寫入),broker裡的org.apache.bookkeeper.mledger.impl.ManagedLedgerImpl(Pulsar項目裡的類)負責将多個ledger串成一個topic/分區,是以隻有ManagedLedgerImpl能知道這些資訊,需要在ManagedLedgerImpl裡實作這一部分資料的搜集,并在ManagedLedgerImpl調用bookie client建立ledger的時候把這部分資料作為參數傳進去。

不過因為給建立Ledger的api增加一個入參的改動量過大,最終并沒有實作這一點。

技術風險

對BookKeeper加入負載均衡的支援并不是很複雜,不涉及到分布式拷貝協定的修改,隻需要在Bookie client建立ledger的時候,插入一個步驟 – 詢問負載均衡器以提供更合理的ensemble集合 即可。插入的這個步驟也不會引入風險,就算失敗了也隻需使用原來的随機化邏輯兜底即可。如下圖所示:

BIGO優化Apache Pulsar系列-Bookie負載均衡

目前的邏輯

BIGO優化Apache Pulsar系列-Bookie負載均衡

修改後的邏輯

收益

bookie負載均衡特性已經上線到生産環境,并取得了較好的效果,在叢集流量壓力沒有變化的情況下,P99寫延遲從秒級甚至幾十秒,下降到1s以内。我們已經将代碼貢獻給了社群,感興趣的讀者可以自行取用:https://github.com/apache/bookkeeper/issues/4247

要權衡一個負載均衡算法的好壞,比較直接的方式就是看我們關心的名額,具體到這裡就是P99寫入延遲,更深入一點,我們可以看叢集整體的負載分布情況,具體到這裡就是journal盤寫入吞吐标準差、journal盤寫入吞吐極差、journal盤寫入吞吐top 10。

下面一一看這些名額的前後對比(于1.10上線):

·P99寫延遲

BIGO優化Apache Pulsar系列-Bookie負載均衡

可以看到,寫延遲從峰值十多二十秒,顯著下降到1s以内。

·journal盤寫入吞吐标準差

BIGO優化Apache Pulsar系列-Bookie負載均衡

journal盤寫吞吐标準差峰值從25MB/s降到21MB/s。

·journal盤寫入吞吐極差

BIGO優化Apache Pulsar系列-Bookie負載均衡

寫吞吐極差峰值從150MB/s降到130MB/s。

·journal盤寫入吞吐top 10

BIGO優化Apache Pulsar系列-Bookie負載均衡

journal盤寫入吞吐top 10也有明顯的下降。

總結

BIGO為Bookie加入負載均衡機制獲得了顯著的收益,不再需要叢集擴容來緩解部分超載節點的壓力,顯著降低了成本。而且增加機器來擴容也不一定能解決延遲高的問題,因為問題本質不是資源不夠,而是負載不均衡。

為Bookie加入負載均衡機制會被挑戰:這不是一個小特性開發,社群沒有做這個事情,我們能不能做?做了有沒有收益?在經過詳盡的調研與分析,基于充分的資料事實面前,這些問題都能一一解答,并最終取得預期内的成果。

作者:BIGO程式員

來源-微信公衆号:BIGO技術

出處:https://mp.weixin.qq.com/s/doNz8tbE-GNZmqzY5PQqaA