Categories
程式開發

Kafka集群突破百萬partition的技術探索


前言

對於小業務量的業務,往往多個業務共享kafka集群,隨著業務規模的增長需要不停的增加topic或者是在原topic的基礎上擴容partition數,另外一些後來大體量的業務在試水階段也可能不會部署獨立的集群,當業務規模爆發時,需要迅速擴容擴容集群節點。在不犧牲穩定性的前提下單集群規模有限,常常會碰到業務體量變大後無法在原集群上直接進行擴容,只能讓業務創建新的集群來支撐新增的業務量,這時用戶面臨系統變更的成本,有時由於業務關聯的原因,集群分開後涉及到業務部署方案的改變,很難短時間解決。

為了快速支持業務擴容,就需要我們在不需要業務方做任何改動的前提下對集群進行擴容,大規模的集群,往往意味著更多的partition數,更多的broker節點,下面會描述當集群規模增長後主要面臨哪些方面的挑戰:

挑戰

ZK節點數

Kafka的topic在broker上是以partition為最小單位存放和進行複制的,因此集群​​需要維護每個partition的Leader信息,單個partition的多個副本都存放在哪些broker節點上,處於復制同步狀態的副本都有哪些。為了存放這些元數據,kafka集群會為每一個partition在zk集群上創建一個節點,partition的數量直接決定了zk上的節點數。

假設集群上有1萬個topic,每個topic包含100個partition,則ZK上節點數約為200多萬個,快照大小約為300MB,ZK節點數據變更,會把數據會寫在事務日誌中進行持久化存儲,當事務日誌達到一定的條目會全量寫入數據到持久化快照文件中,partition節點數擴大意味著快照文件也大,全量寫入快照與事務日誌的寫入會相互影響,從而影響客戶端的響應速度,同時zk節點重啟加載快照的時間也會變長。

Partition複製

Kafka的partition複製由獨立的複制線程負責,多個partition會共用複制線程,當單個broker上的partition增大以後,單個複制線程負責的partition數也會增多,每個partition對應一個日誌文件,當大量的partition同時有寫入時,磁盤上文件的寫入也會更分散,寫入性能變差,可能出現複製跟不上,導致ISR頻繁波動,調整復制線程的數量可以減少單個線程負責的partition數量,但是也加劇了磁盤的爭用。

Controller切換時長

由於網絡或者機器故障等原因,運行中的集群可能存在controller切換的情況,當controller切換時需要從ZK中恢復broker節點信息、topic的partition複製關係、partition當前leader在哪個節點上等,然後會把partition完整的信息同步給每一個broker節點。

在虛擬機上測試,100萬partition的元數據從ZK恢復到broker上約需要37s的時間,100萬partition生成的元數據序列化後大約80MB(數據大小與副本數、topic名字長度等相關),其他broker接收到元數據後,進行反序列化並更新到本機broker內存中,應答響應時間約需要40s(測試時長與網絡環境有關)。

Controller控制了leader切換與元數據的下發給集群中其他broker節點,controller的恢復時間變長增加了集群不可用風險,當controller切換時如果存在partition的Leader需要切換,就可能存在客戶端比較長的時間內拿不到新的leader,導致服務中斷。

broker上下線恢復時長

日常維護中可能需要對broker進行重啟操作,為了不影響用戶使用,broker在停止前會通知controller進行Leader切換,同樣broker故障時也會進行leader切換,leader切換信息需要更新ZK上的partition狀態節點數據,並同步給其他的broker進行metadata信息更新。當partition數量變多,意味著單個broker節點上的partitiion Leader切換時間變長。

通過上述幾個影響因素,我們知道當partition數量增加時會直接影響到controller故障恢復時間;單個broker上partition數量增多會影響磁盤性能,複製的穩定性;broker重啟Leader切換時間增加等。當然我們完全可以在現有的架構下限制每個broker上的partition數量,來規避單broker上受partition數量的影響,但是這樣意味著集群內broker節點數會增加,controller負責的broker節點數增加,同時controller需要管理的partition數並不會減少,如果我們想解決大量partition共用一個集群的場景,那麼核心需要解決的問題就是要么提升單個controller的處理性能能力,要么增加controller的數量。

解決方案

單ZK集群

從提升單個controller處理性能方面可以進行下面的優化:

並行拉取zk節點

Controller在拉取zk上的元數據時,雖然採用了異步等待數據響應的方式,請求和應答非串行等待,但是單線程處理消耗了大約37s,我們可以通過多線程並行拉取元數據,每個線程負責一部分partition,從而縮減拉取元數據的時間。
在虛擬機上簡單模擬獲取100萬個節點數據,單線程約花費28s,分散到5個線程上並行處理,每個線程負責20萬pa​​rtition數據的拉取,總時間縮短為14s左右(這個時間受虛擬機本身性能影響,同虛擬機上如果單線程拉取20萬pa​​rtition約只需要6s左右),因此在controller恢復時,並行拉取partition可以明顯縮短恢復時間。

變更同步元數據的方式

上文中提到100萬partition生成的元數據約80MB,如果我們限制了單broker上partition數量,意味著我們需要增加broker的節點數,controller切換並行同步給大量的broker,會給controller節點帶來流量的衝擊,同時同步80MB的元數據也會消耗比較長的時間。因此需要改變現在集群同步元數據的方式,比如像存放消費位置一樣,通過內置topic來存放元數據,controller把寫入到ZK上的數據通過消息的方式發送到內置存放元數據的topic上,broker分別從topic上消費這些數據並更新內存中的元數據,這類的方案雖然可以在controller切換時全量同步元數據,但是需要對現在的kafka架構進行比較大的調整(當然還有其他更多的辦法,比如不使用ZK來管理元數據等,不過這不在本篇文章探討的範圍內)。

那有沒有其他的辦法,在對kafka架構改動較小的前提下來支持大規模partition的場景呢?我們知道kafka客戶端與broker交互時,會先通過指定的地址拉取topic元數據,然後再根據元數據連接partition相應的Leader進行生產和消費,我們通過控制元數據,可以控制客戶端生產消費連接的機器,這些機器在客戶端並不要求一定在同一個集群中,只需要客戶端能夠拿到這些partition的狀態信息,因此我們可以讓不同的topic分佈到不同的集群上,然後再想辦法把不同集群上的topic信息組合在一起返回給客戶端,就能達到客戶端同時連接不同集群的效果,從客戶端視角來看就就是一個大的集群。這樣不需要單個物理集群支撐非常大的規模,可以通過組合多個物理集群的方式來達到支撐更大的規模,通過這種方式,擴容時不需要用戶停機修改業務,下面我們就來描述一下怎麼實現這種方案:

小集群組建邏輯集群

當我們需要組建邏輯集群時,有幾個核心問題需要解決:

1、當客戶端需要拉取元數據時,怎麼把多個小的物理集群上的元數據組裝在一起返回給客戶端;

2、不同集群上的元數據變更時怎麼及時地通知變更;

3、多個集群上保存消費位置和事務狀態的topic怎麼分佈。
下面針對這些問題一一進行講解:

Kafka集群突破百萬partition的技術探索 1

metadata服務

針對metadata組裝問題,我們可以在邏輯集群裡的多個物理集群中選一個為主集群,其他集群為擴展集群,由主集群負責對外提供metadata、消費位置、事務相關的服務,當然主集群也可以同時提供消息的生產消費服務,擴展集群只能用於業務消息的生產和消費。我們知道當partition的Leader切換時需要通過集群中的controller把新的metadata數據同步給集群中的broker。當邏輯集群是由多個相互獨立的物理集群組成時,controller無法感知到其他集群中的Broker節點。

我們可以對主集群中的metada接口進行簡單的改造,當客戶端拉取metadata時,我們可以跳轉到其他的集群上拉取metadata,然後在主集群上進行融合組裝再返回給客戶端。

雖然跳轉拉取metadata的方式有一些性能上的消耗,但是正常情況下並不在消息生產和消費的路徑上,對客戶端影響不大。通過客戶端拉取時再組裝metadata,可以規避跨物理集群更新metadata的問題,同時也能夠保證實時性。

消費分組與事務協調

當消費分組之間的成員需要協調拉取數據的partition時,服務端會根據保存消費位置topic的partition信息返回對應的協調節點,因此我們在一個邏輯集群中需要確定消費位置topic分佈的集群,避免訪問不同物理集群的節點返回的協調者不一樣,從不同集群上拉取到的消費位置不一樣等問題。我們可以選主集群的broker節點提供消費和事務協調的服務,消費位置也只保存在主集群上。

通過上述的一些改造,我們就可以支持更大的業務規模,用戶在使用時只需要知道主集群的地址就可以了。

組建邏輯集群除了上述的核心問題外,我們也需要關注topic的分配,由於騰訊雲的ckafka本身就會把broker上創建topic的請求轉發給管控模塊創建,因此可以很方便的解決topic在多個物理集群的分佈,也可以規避同一邏輯集群上,不同物理集群內可能出現同名topic的問題。

單物理集群分裂

Kafka集群突破百萬partition的技術探索 2Kafka集群突破百萬partition的技術探索 3

前面講述了多個物理集群怎麼組建成單個邏輯集群,有時可能面臨一個問題,就是單個物理集群由於一些原因需要在現有的topic上不斷的擴充partition,如果多個topic同時需要擴容可能出現單個物理集群過大的情況,因此需要對現有的集群進行分裂,一個物理集群拆分成兩個物理集群。

進行集群的分裂涉及到ZK集群的分裂和對broker節點進行分組拆分,首先對集群中的broker節點分成兩組,每組連接不同的ZK節點,比如我們可以在原來的zk集群中增加observer節點,新增的broker為一組,原來集群中的broker為一組,我們讓新broker只填寫observer的地址。 ZK集群分裂前,通過KAFKA內置遷移工具可以很方便地把不同的topic遷移到各自的broker分組上,同一個topic的partition只會分佈在同一個分組的broker節點上,後續把observer節點從現有的ZK集群中移除出去,然後讓observer與別的ZK節點組成新的ZK集群,從而實現kafka集群的分裂。

結束語

通過提升controller的性能,和通過把多個物理集群組裝成一個邏輯集群的做法都可以提升單集群承載partition的規模。但是相比而言,通過組建多個物理集群的方式對kafka現有的架構改動更小一些,故障恢復的時間上更有保障一些,服務更穩定。

當然業務在使用kafka服務時,如果業務允許保持一個partition數量適度的集群規模,通過業務拆分的方式連接不同的集群也是一種很好的實踐方式。

本篇文章主要從元數據,controller邏輯等方面介紹瞭如何解決支撐百萬partition的問題,運營大規模集群其實還涉及到磁盤故障,冷讀,數據均衡等數據方面的問題,監控和報警服務同樣非常的重要。騰訊雲的CKAFKA團隊一直在不斷探索,致力於為用戶提供可靠的消息服務。

作者簡介

丁俊,主要從事消息、緩存、NOSQL等基礎設施的研發,目前就職於騰訊雲。