深度:Kafka 集群突破百萬 partition 的技術(shù)探索
導(dǎo)語:本篇文章主要從元數(shù)據(jù),controller 邏輯等方面介紹了如何解決支撐百萬 partition 的問題,運營大規(guī)模集群其實還涉及到磁盤故障、冷讀、數(shù)據(jù)均衡等數(shù)據(jù)方面的問題,監(jiān)控和報警服務(wù)同樣非常的重要。
本文來源:騰訊云中間件
對于小業(yè)務(wù)量的業(yè)務(wù),往往多個業(yè)務(wù)共享 kafka 集群,隨著業(yè)務(wù)規(guī)模的增長需要不停的增加 topic 或者是在原 topic 的基礎(chǔ)上擴容 partition 數(shù),另外一些后來大體量的業(yè)務(wù)在試水階段也可能不會部署獨立的集群,當(dāng)業(yè)務(wù)規(guī)模爆發(fā)時,需要迅速擴容擴容集群節(jié)點。在不犧牲穩(wěn)定性的前提下單集群規(guī)模有限,常常會碰到業(yè)務(wù)體量變大后無法在原集群上直接進行擴容,只能讓業(yè)務(wù)創(chuàng)建新的集群來支撐新增的業(yè)務(wù)量,這時用戶面臨系統(tǒng)變更的成本,有時由于業(yè)務(wù)關(guān)聯(lián)的原因,集群分開后涉及到業(yè)務(wù)部署方案的改變,很難短時間解決。
為了快速支持業(yè)務(wù)擴容,就需要我們在不需要業(yè)務(wù)方做任何改動的前提下對集群進行擴容,大規(guī)模的集群,往往意味著更多的 partition 數(shù),更多的 broker 節(jié)點,下面會描述當(dāng)集群規(guī)模增長后主要面臨哪些方面的挑戰(zhàn)。
Kafka 的 topic 在 broker 上是以 partition 為最小單位存放和進行復(fù)制的, 因此集群需要維護每個 partition 的 Leader 信息,單個 partition 的多個副本都存放在哪些 broker 節(jié)點上,處于復(fù)制同步狀態(tài)的副本都有哪些。為了存放這些元數(shù)據(jù),kafka 集群會為每一個 partition 在 zk 集群上創(chuàng)建一個節(jié)點,partition 的數(shù)量直接決定了 zk 上的節(jié)點數(shù)。
假設(shè)集群上有 1 萬個 topic,每個 topic 包含 100 個 partition,則 ZK 上節(jié)點數(shù)約為 200 多萬個,快照大小約為 300MB,ZK 節(jié)點數(shù)據(jù)變更,會把數(shù)據(jù)會寫在事務(wù)日志中進行持久化存儲,當(dāng)事務(wù)日志達到一定的條目會全量寫入數(shù)據(jù)到持久化快照文件中,partition 節(jié)點數(shù)擴大意味著快照文件也大,全量寫入快照與事務(wù)日志的寫入會相互影響,從而影響客戶端的響應(yīng)速度,同時 zk 節(jié)點重啟加載快照的時間也會變長。
2. Partition 復(fù)制
Kafka 的 partition 復(fù)制由獨立的復(fù)制線程負責(zé),多個 partition 會共用復(fù)制線程,當(dāng)單個 broker 上的 partition 增大以后,單個復(fù)制線程負責(zé)的 partition 數(shù)也會增多,每個 partition 對應(yīng)一個日志文件,當(dāng)大量的 partition 同時有寫入時,磁盤上文件的寫入也會更分散,寫入性能變差,可能出現(xiàn)復(fù)制跟不上,導(dǎo)致 ISR 頻繁波動,調(diào)整復(fù)制線程的數(shù)量可以減少單個線程負責(zé)的 partition 數(shù)量,但是也加劇了磁盤的爭用。
3. Controller 切換時長
由于網(wǎng)絡(luò)或者機器故障等原因,運行中的集群可能存在 controller 切換的情況,當(dāng) controller 切換時需要從 ZK 中恢復(fù) broker 節(jié)點信息、topic 的 partition 復(fù)制關(guān)系、partition 當(dāng)前 leader 在哪個節(jié)點上等,然后會把 partition 完整的信息同步給每一個 broker 節(jié)點。
在虛擬機上測試,100 萬 partition 的元數(shù)據(jù)從 ZK 恢復(fù)到 broker 上約需要 37s 的時間,100 萬 partition 生成的元數(shù)據(jù)序列化后大約 80MB(數(shù)據(jù)大小與副本數(shù)、topic 名字長度等相關(guān)),其他 broker 接收到元數(shù)據(jù)后,進行反序列化并更新到本機 broker 內(nèi)存中,應(yīng)答響應(yīng)時間約需要 40s(測試時長與網(wǎng)絡(luò)環(huán)境有關(guān))。
Controller 控制了 leader 切換與元數(shù)據(jù)的下發(fā)給集群中其他 broker 節(jié)點,controller 的恢復(fù)時間變長增加了集群不可用風(fēng)險,當(dāng) controller 切換時如果存在 partition 的 Leader 需要切換,就可能存在客戶端比較長的時間內(nèi)拿不到新的 leader,導(dǎo)致服務(wù)中斷。
4. broker 上下線恢復(fù)時長
日常維護中可能需要對 broker 進行重啟操作,為了不影響用戶使用,broker 在停止前會通知 controller 進行 Leader 切換,同樣 broker 故障時也會進行 leader 切換,leader 切換信息需要更新 ZK 上的 partition 狀態(tài)節(jié)點數(shù)據(jù),并同步給其他的 broker 進行 metadata 信息更新。當(dāng) partition 數(shù)量變多,意味著單個 broker 節(jié)點上的 partitiion Leader 切換時間變長。
通過上述幾個影響因素,我們知道當(dāng) partition 數(shù)量增加時會直接影響到 controller 故障恢復(fù)時間;單個 broker 上 partition 數(shù)量增多會影響磁盤性能,復(fù)制的穩(wěn)定性;broker 重啟 Leader 切換時間增加等。當(dāng)然我們完全可以在現(xiàn)有的架構(gòu)下限制每個 broker 上的 partition 數(shù)量,來規(guī)避單 broker 上受 partition 數(shù)量的影響,但是這樣意味著集群內(nèi) broker 節(jié)點數(shù)會增加,controller 負責(zé)的 broker 節(jié)點數(shù)增加,同時 controller 需要管理的 partition 數(shù)并不會減少,如果我們想解決大量 partition 共用一個集群的場景,那么核心需要解決的問題就是要么提升單個 controller 的處理性能能力,要么增加 controller 的數(shù)量。
1. 單 ZK 集群
從提升單個 controller 處理性能方面可以進行下面的優(yōu)化:
并行拉取 zk 節(jié)點
Controller 在拉取 zk 上的元數(shù)據(jù)時,雖然采用了異步等待數(shù)據(jù)響應(yīng)的方式,請求和應(yīng)答非串行等待,但是單線程處理消耗了大約 37s,我們可以通過多線程并行拉取元數(shù)據(jù),每個線程負責(zé)一部分 partition,從而縮減拉取元數(shù)據(jù)的時間。
在虛擬機上簡單模擬獲取 100 萬個節(jié)點數(shù)據(jù),單線程約花費 28s,分散到 5 個線程上并行處理,每個線程負責(zé) 20 萬 partition 數(shù)據(jù)的拉取,總時間縮短為 14s 左右(這個時間受虛擬機本身性能影響,同虛擬機上如果單線程拉取 20 萬 partition 約只需要 6s 左右),因此在 controller 恢復(fù)時,并行拉取 partition 可以明顯縮短恢復(fù)時間。
變更同步元數(shù)據(jù)的方式
上文中提到 100 萬 partition 生成的元數(shù)據(jù)約 80MB,如果我們限制了單 broker 上 partition 數(shù)量,意味著我們需要增加 broker 的節(jié)點數(shù),controller 切換并行同步給大量的 broker,會給 controller 節(jié)點帶來流量的沖擊,同時同步 80MB 的元數(shù)據(jù)也會消耗比較長的時間。因此需要改變現(xiàn)在集群同步元數(shù)據(jù)的方式,比如像存放消費位置一樣,通過內(nèi)置 topic 來存放元數(shù)據(jù),controller 把寫入到 ZK 上的數(shù)據(jù)通過消息的方式發(fā)送到內(nèi)置存放元數(shù)據(jù)的 topic 上,broker 分別從 topic 上消費這些數(shù)據(jù)并更新內(nèi)存中的元數(shù)據(jù),這類的方案雖然可以在 controller 切換時全量同步元數(shù)據(jù),但是需要對現(xiàn)在的 kafka 架構(gòu)進行比較大的調(diào)整(當(dāng)然還有其他更多的辦法,比如不使用 ZK 來管理元數(shù)據(jù)等,不過這不在本篇文章探討的范圍內(nèi))。
那有沒有其他的辦法,在對 kafka 架構(gòu)改動較小的前提下來支持大規(guī)模 partition 的場景呢?我們知道 kafka 客戶端與 broker 交互時,會先通過指定的地址拉取 topic 元數(shù)據(jù),然后再根據(jù)元數(shù)據(jù)連接 partition 相應(yīng)的 Leader 進行生產(chǎn)和消費,我們通過控制元數(shù)據(jù),可以控制客戶端生產(chǎn)消費連接的機器,這些機器在客戶端并不要求一定在同一個集群中,只需要客戶端能夠拿到這些 partition 的狀態(tài)信息,因此我們可以讓不同的 topic 分布到不同的集群上,然后再想辦法把不同集群上的 topic 信息組合在一起返回給客戶端,就能達到客戶端同時連接不同集群的效果,從客戶端視角來看就就是一個大的集群。這樣不需要單個物理集群支撐非常大的規(guī)模,可以通過組合多個物理集群的方式來達到支撐更大的規(guī)模,通過這種方式,擴容時不需要用戶停機修改業(yè)務(wù),下面我們就來描述一下怎么實現(xiàn)這種方案。
2. 小集群組建邏輯集群
下面針對這些問題一一進行講解:

metadata 服務(wù)
針對 metadata 組裝問題,我們可以在邏輯集群里的多個物理集群中選一個為主集群,其他集群為擴展集群,由主集群負責(zé)對外提供 metadata、消費位置、事務(wù)相關(guān)的服務(wù),當(dāng)然主集群也可以同時提供消息的生產(chǎn)消費服務(wù),擴展集群只能用于業(yè)務(wù)消息的生產(chǎn)和消費。我們知道當(dāng) partition 的 Leader 切換時需要通過集群中的 controller 把新的 metadata 數(shù)據(jù)同步給集群中的 broker。當(dāng)邏輯集群是由多個相互獨立的物理集群組成時,controller 無法感知到其他集群中的 Broker 節(jié)點。
我們可以對主集群中的 metada 接口進行簡單的改造,當(dāng)客戶端拉取 metadata 時,我們可以跳轉(zhuǎn)到其他的集群上拉取 metadata, 然后在主集群上進行融合組裝再返回給客戶端。
雖然跳轉(zhuǎn)拉取 metadata 的方式有一些性能上的消耗,但是正常情況下并不在消息生產(chǎn)和消費的路徑上,對客戶端影響不大。通過客戶端拉取時再組裝 metadata,可以規(guī)避跨物理集群更新 metadata 的問題,同時也能夠保證實時性。
消費分組與事務(wù)協(xié)調(diào)
當(dāng)消費分組之間的成員需要協(xié)調(diào)拉取數(shù)據(jù)的 partition 時,服務(wù)端會根據(jù)保存消費位置 topic 的 partition 信息返回對應(yīng)的協(xié)調(diào)節(jié)點,因此我們在一個邏輯集群中需要確定消費位置 topic 分布的集群,避免訪問不同物理集群的節(jié)點返回的協(xié)調(diào)者不一樣,從不同集群上拉取到的消費位置不一樣等問題。我們可以選主集群的 broker 節(jié)點提供消費和事務(wù)協(xié)調(diào)的服務(wù),消費位置也只保存在主集群上。
通過上述的一些改造,我們就可以支持更大的業(yè)務(wù)規(guī)模,用戶在使用時只需要知道主集群的地址就可以了。
組建邏輯集群除了上述的核心問題外,我們也需要關(guān)注 topic 的分配,由于騰訊云的 ckafka 本身就會把 broker 上創(chuàng)建 topic 的請求轉(zhuǎn)發(fā)給管控模塊創(chuàng)建,因此可以很方便的解決 topic 在多個物理集群的分布,也可以規(guī)避同一邏輯集群上,不同物理集群內(nèi)可能出現(xiàn)同名 topic 的問題。
單物理集群分裂

進行集群的分裂涉及到 ZK 集群的分裂和對 broker 節(jié)點進行分組拆分,首先對集群中的 broker 節(jié)點分成兩組,每組連接不同的 ZK 節(jié)點,比如我們可以在原來的 zk 集群中增加 observer 節(jié)點,新增的 broker 為一組,原來集群中的 broker 為一組,我們讓新 broker 只填寫 observer 的地址。ZK 集群分裂前,通過 KAFKA 內(nèi)置遷移工具可以很方便地把不同的 topic 遷移到各自的 broker 分組上,同一個 topic 的 partition 只會分布在同一個分組的 broker 節(jié)點上,后續(xù)把 observer 節(jié)點從現(xiàn)有的 ZK 集群中移除出去,然后讓 observer 與別的 ZK 節(jié)點組成新的 ZK 集群,從而實現(xiàn) kafka 集群的分裂。
通過提升 controller 的性能,和通過把多個物理集群組裝成一個邏輯集群的做法都可以提升單集群承載 partition 的規(guī)模。但是相比而言,通過組建多個物理集群的方式對 kafka 現(xiàn)有的架構(gòu)改動更小一些,故障恢復(fù)的時間上更有保障一些,服務(wù)更穩(wěn)定。
當(dāng)然業(yè)務(wù)在使用 kafka 服務(wù)時,如果業(yè)務(wù)允許保持一個 partition 數(shù)量適度的集群規(guī)模,通過業(yè)務(wù)拆分的方式連接不同的集群也是一種很好的實踐方式。
丁俊,騰訊云消息隊列 Ckafka 負責(zé)人,擁有多年消息、緩存、NOSQL 等基礎(chǔ)設(shè)施的研發(fā)經(jīng)驗。騰訊云 CKafka 團隊一直在不斷探索,致力于為用戶提供可靠的消息服務(wù)。
