與故障排查)
1. 從“會用的中間件”到“懂原理的工程師”為什么我要啃 Kafka Raft很多人用 Kafka 用了好幾年日常就是寫 Producer、調(diào) Consumer、偶爾看看 Lag覺得這套東西“會用就行”。但一旦集群出現(xiàn) Controller 頻繁切換、元數(shù)據(jù)不一致、Partition Leader 選舉卡住這類問題光靠翻文檔和重啟大法就完全不夠用了。我真正下決心把 Kafka Raft 從頭到尾捋一遍就是因為一次線上事故一個三節(jié)點的元數(shù)據(jù)集群在滾動重啟后Controller 反復易主日志里全是選舉超時和日志截斷相關(guān)的報錯當時團隊里沒人能說清楚底層到底發(fā)生了什么。Kafka Raft準確說是KRaftKafka Raft Metadata Mode是 Kafka 用來替代 ZooKeeper 做元數(shù)據(jù)管理的共識協(xié)議實現(xiàn)。它把原本托管在外部協(xié)調(diào)組件里的元數(shù)據(jù)——比如 Broker 注冊、Topic 配置、分區(qū)副本分配、ACL、ISR 變更——全部收回到 Kafka 自己內(nèi)部通過一個 Raft 共識組來維護。這件事的意義不只是“少部署一個組件”而是讓元數(shù)據(jù)的讀寫路徑、一致性模型、故障恢復邏輯都統(tǒng)一到同一套體系里。這篇文章適合三類人看第一類是把 Kafka 當黑盒用、但想搞清楚元數(shù)據(jù)層到底怎么運轉(zhuǎn)的開發(fā)和運維第二類是正在做 Kafka 版本升級、考慮從 ZooKeeper 模式遷移到 KRaft 的架構(gòu)同學第三類是準備面試、被問到“Kafka 為什么能去掉 ZooKeeper”卻答不深的朋友。我會從設計思路、核心概念、實操部署、故障排查幾個角度把 KRaft 這套東西拆開講透盡量做到看完能自己搭一套、出問題能自己定位。2. KRaft 的整體設計與核心思路拆解2.1 為什么非要干掉 ZooKeeper要理解 KRaft得先理解 ZooKeeper 模式下的痛點。在舊架構(gòu)里Kafka 的元數(shù)據(jù)分成兩半一部分存在 ZooKeeper 的 znode 里比如 Broker 列表、Topic 配置、Controller 選舉另一部分存在 Controller 的內(nèi)存里比如分區(qū)狀態(tài)機、副本分配。這兩半之間靠 Controller 監(jiān)聽 ZooKeeper 事件來同步問題就出在這個“同步”上。當集群規(guī)模變大比如上千個 Broker、幾萬個分區(qū)Controller 需要處理的元數(shù)據(jù)變更事件會急劇膨脹。ZooKeeper 的 watch 機制在大量事件同時觸發(fā)時Controller 會收到海量通知然后串行處理很容易成為瓶頸。更麻煩的是Controller 切換時新 Controller 必須先從 ZooKeeper 全量拉取元數(shù)據(jù)再重建內(nèi)存狀態(tài)這個過程中整個集群的元數(shù)據(jù)操作基本處于半停滯狀態(tài)。我見過一個中等規(guī)模集群Controller 切換一次要花好幾分鐘期間新建 Topic、分區(qū)重分配全部卡住。KRaft 的思路是把元數(shù)據(jù)當成一條日志流來管理。所有元數(shù)據(jù)變更都寫成一條條記錄追加到一個 Raft 日志里然后由 Raft 協(xié)議保證這條日志在多個節(jié)點之間一致復制。每個節(jié)點都按相同順序回放這條日志最終得到完全一致的狀態(tài)機。這樣一來元數(shù)據(jù)不再有“兩半”而是單一事實來源Controller 也不再是單點瓶頸因為元數(shù)據(jù)日志本身就是高可用的。2.2 Raft 在 Kafka 里的角色定位Raft 本身是一個共識算法核心解決的是“多個節(jié)點如何就一系列操作達成一致”。它把問題拆成三塊Leader 選舉、日志復制、安全性約束。Kafka 并沒有直接照搬某個現(xiàn)成的 Raft 庫而是自己實現(xiàn)了一套原因在于 Kafka 的元數(shù)據(jù)場景有特殊需求。第一個特殊點是元數(shù)據(jù)日志和消息日志的分離。KRaft 里的 Raft 日志只存元數(shù)據(jù)不存用戶消息。用戶消息還是走原來的 Partition 副本機制。這樣 Raft 組的負載相對可控不會被消息流量拖垮。第二個特殊點是快照機制。Raft 日志不能無限增長否則重啟回放會越來越慢。KRaft 會定期對狀態(tài)機做快照然后把快照之前的日志截斷??煺绽锎娴氖钱斍巴暾脑獢?shù)據(jù)狀態(tài)新加入的節(jié)點或者落后太多的節(jié)點可以直接拉快照不用從第一條日志開始回放。第三個特殊點是動態(tài)成員變更。KRaft 支持在運行中調(diào)整 Controller 節(jié)點集合比如從三節(jié)點擴到五節(jié)點。這個過程中要保證不出現(xiàn)兩個 Leader 同時存在的情況Raft 用聯(lián)合共識joint consensus的思路來處理Kafka 的實現(xiàn)里叫“動態(tài) Quorum 重配置”。2.3 Controller、Broker、Quorum 三者的關(guān)系在 KRaft 模式下節(jié)點角色可以分成兩類Controller和Broker。Controller 負責元數(shù)據(jù)管理Broker 負責消息讀寫。一個節(jié)點可以同時扮演兩個角色也可以只做其中一個。生產(chǎn)環(huán)境里通常會把 Controller 和 Broker 分開部署避免元數(shù)據(jù)操作和消息流量互相干擾。Controller 們組成一個Quorum也就是 Raft 組。Quorum 里有一個 Active ControllerLeader其余是 Standby。所有元數(shù)據(jù)寫請求都發(fā)給 Active Controller它把變更寫成日志條目復制給多數(shù)派然后應用到自己的狀態(tài)機。Standby 們持續(xù)從 Leader 拉日志保持狀態(tài)同步。一旦 Leader 掛了Standby 里會重新選出一個 Leader整個過程通常在秒級完成。Broker 則通過一個叫BrokerRegistration的機制向 Quorum 注冊自己定期發(fā)送心跳。如果 Broker 長時間不心跳Quorum 會把它標記為下線并觸發(fā)分區(qū) Leader 重選舉。這里的關(guān)鍵是Broker 不直接參與 Raft 共識它只是元數(shù)據(jù)的消費者和生產(chǎn)者通過 Controller 來間接操作元數(shù)據(jù)。3. 核心概念與關(guān)鍵機制深度解析3.1 日志、任期、索引Raft 的三塊基石Raft 日志里的每一條記錄都有一個偏移量Offset從 0 開始單調(diào)遞增。這個偏移量是全局唯一的所有節(jié)點上的同一條日志必須有相同的偏移量。日志條目里除了元數(shù)據(jù)變更內(nèi)容還帶著一個任期號Term。任期號是個單調(diào)遞增的整數(shù)每次 Leader 選舉都會加一。任期號的作用是區(qū)分不同 Leader 的日志防止舊 Leader 的殘留日志被誤認為有效。選舉過程中候選人會把自己的任期號加一然后向其他節(jié)點拉票。其他節(jié)點如果發(fā)現(xiàn)候選人的任期號比自己大就更新自己的任期號并投票如果發(fā)現(xiàn)候選人的日志不如自己新就拒絕投票。這里“日志新不新”的比較規(guī)則是先比最后一條日志的任期號任期號大的更新任期號相同再比偏移量偏移量大的更新。這個規(guī)則保證了新選出的 Leader 一定擁有最完整的日志不會丟失已經(jīng)提交的條目。索引Index在 KRaft 里有兩個層面。一個是日志偏移量另一個是快照索引??煺帐菍顟B(tài)機在某個偏移量上的完整記錄。當節(jié)點重啟時它會先加載最新的快照然后從快照對應的偏移量之后開始回放日志。這樣就不用從零開始重建狀態(tài)啟動速度大幅提升。3.2 Leader 選舉什么時候選、怎么選、選不出來怎么辦Leader 選舉的觸發(fā)條件主要有三個集群啟動時沒有 Leader、Leader 心跳超時、Leader 主動退位。心跳超時時間由controller.quorum.election.timeout.ms控制默認是 1000 毫秒。也就是說如果 Follower 超過 1 秒沒收到 Leader 的心跳就會認為 Leader 掛了開始新一輪選舉。選舉過程是這樣的Follower 先變成 Candidate任期號加一投自己一票然后向其他所有節(jié)點發(fā)送 RequestVote 請求。其他節(jié)點收到請求后先比較任期號再比較日志新舊決定是否投票。如果 Candidate 拿到了多數(shù)派的票就變成 Leader開始發(fā)送心跳。如果這一輪沒選出來就等待一個隨機超時controller.quorum.election.backoff.max.ms然后任期號再加一重新發(fā)起選舉。選不出來最常見的原因是網(wǎng)絡分區(qū)或者節(jié)點數(shù)不滿足多數(shù)派。比如三節(jié)點 Quorum 掛了一個剩下兩個還能選掛兩個剩下一個永遠選不出來因為拿不到多數(shù)派。這時候集群會進入不可用狀態(tài)元數(shù)據(jù)寫操作全部阻塞。所以生產(chǎn)環(huán)境里 Quorum 節(jié)點數(shù)必須是奇數(shù)三節(jié)點能容忍一個故障五節(jié)點能容忍兩個。3.3 日志復制與提交數(shù)據(jù)怎么寫進去、什么時候算寫成功客戶端發(fā)來的元數(shù)據(jù)寫請求比如創(chuàng)建 Topic會先到 Active Controller。Controller 把請求轉(zhuǎn)成一條日志條目追加到自己的本地日志末尾然后并行發(fā)送給所有 Follower。Follower 收到后先寫入本地日志然后返回確認。當 Leader 收到多數(shù)派的確認后就把這條日志標記為已提交Committed然后應用到狀態(tài)機最后返回給客戶端成功。這里有個關(guān)鍵點已提交的日志一定會被所有未來的 Leader 包含。Raft 通過選舉限制來保證這一點——只有擁有最完整日志的節(jié)點才能當選 Leader。所以即使某個 Follower 落后很多它也不可能在落后狀態(tài)下當選從而避免了已提交數(shù)據(jù)丟失。日志復制是流水線式的Leader 會維護每個 Follower 的nextIndex和matchIndex。nextIndex是下一條要發(fā)給該 Follower 的日志偏移量matchIndex是該 Follower 已經(jīng)確認復制的最高偏移量。如果 Follower 的日志和 Leader 不一致Leader 會逐步回退nextIndex直到找到一致點然后從那里開始覆蓋 Follower 的沖突日志。這個過程叫日志修復。3.4 快照機制日志不能無限長怎么截斷Raft 日志如果一直不清理磁盤會爆重啟回放也會慢到無法接受。KRaft 的解決辦法是定期做快照??煺盏膬?nèi)容是當前狀態(tài)機的完整序列化比如所有 Topic 的配置、所有分區(qū)的副本分配、所有 Broker 的注冊信息??煺丈珊髮娜罩揪涂梢员唤財嘀槐A艨煺拯c之后的日志??煺盏挠|發(fā)條件由controller.quorum.snapshot.interval.ms控制默認是 10 分鐘。也就是說每隔 10 分鐘Controller 會把當前狀態(tài)機做一次快照。如果日志增長太快也可以基于日志條數(shù)觸發(fā)由controller.quorum.snapshot.interval.records控制。新節(jié)點加入 Quorum 時如果它的日志落后太多Leader 會直接發(fā)一個快照給它而不是逐條回放日志。這個機制叫InstallSnapshot。快照傳輸是分塊的避免一次性傳輸大文件導致網(wǎng)絡阻塞。節(jié)點收到快照后先加載快照再從快照點開始拉后續(xù)日志。4. 實操部署從零搭一套 KRaft 集群4.1 環(huán)境準備與版本選擇KRaft 模式從 Kafka 2.8 開始有實驗性支持3.3 之后基本穩(wěn)定3.5 以后生產(chǎn)可用。我建議至少用 3.6 或更高版本因為早期版本在動態(tài) Quorum 重配置和快照恢復上還有一些邊界問題。JDK 用 17 或 21 都行Kafka 對 JDK 17 的支持已經(jīng)很成熟。假設我們要搭一個三節(jié)點 Quorum 三節(jié)點 Broker 的集群但為了演示方便先用三個節(jié)點同時扮演 Controller 和 Broker 角色。每臺機器配置 4 核 8G 起步磁盤用 SSD因為 Raft 日志對 fsync 性能比較敏感。網(wǎng)絡延遲要低Quorum 節(jié)點之間最好在同一個機房或同一個可用區(qū)跨機房部署會顯著增加選舉超時和日志復制延遲。4.2 關(guān)鍵配置項逐條解讀KRaft 模式下server.properties里最核心的幾個配置如下process.rolescontroller,broker node.id1 controller.quorum.voters1node1:9093,2node2:9093,3node3:9093 listenersPLAINTEXT://node1:9092,CONTROLLER://node1:9093 controller.listener.namesCONTROLLER listener.security.protocol.mapCONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT log.dirs/data/kafka-logs controller.quorum.election.timeout.ms1000 controller.quorum.election.backoff.max.ms1000 controller.quorum.fetch.timeout.ms2000process.roles決定這個節(jié)點是 Controller、Broker 還是兩者兼有。生產(chǎn)環(huán)境建議分開用獨立的 Controller 節(jié)點避免 Broker 的 IO 壓力影響元數(shù)據(jù)一致性。controller.quorum.voters是靜態(tài) Quorum 配置格式是idhost:port所有節(jié)點必須配置一致。controller.listener.names指定 Controller 通信用的監(jiān)聽器名稱必須和listeners里的對應。controller.quorum.election.timeout.ms是選舉超時默認 1000 毫秒。如果網(wǎng)絡延遲高可以適當調(diào)大比如 2000 毫秒。但調(diào)太大意味著故障發(fā)現(xiàn)變慢Leader 掛了之后要等更久才能重新選出來。controller.quorum.fetch.timeout.ms是 Follower 拉日志的超時默認 2000 毫秒通常不用改。4.3 格式化與啟動流程第一次啟動前必須對每個節(jié)點做格式化生成初始的元數(shù)據(jù)目錄。命令是bin/kafka-storage.sh random-uuid這個命令會生成一個集群 UUID所有節(jié)點必須用同一個 UUID。然后對每個節(jié)點執(zhí)行bin/kafka-storage.sh format -t cluster-uuid -c config/kraft/server.properties格式化會創(chuàng)建log.dirs下的meta.properties文件里面記錄了集群 ID 和節(jié)點 ID。注意格式化只能在集群第一次啟動前做如果已經(jīng)有數(shù)據(jù)重新格式化會清空所有元數(shù)據(jù)相當于把集群重置了。啟動順序上先啟動所有 Controller 節(jié)點等 Quorum 選出一個 Active Controller 后再啟動 Broker 節(jié)點??梢杂胋in/kafka-metadata-quorum.sh --bootstrap-server node1:9092 describe --status查看 Quorum 狀態(tài)確認 Leader 是誰、Follower 是否同步。4.4 驗證集群是否正常集群起來后先創(chuàng)建一個測試 Topicbin/kafka-topics.sh --bootstrap-server node1:9092 --create --topic test-kraft --partitions 3 --replication-factor 3然后查看 Topic 描述確認分區(qū) Leader 和 ISR 都正常。再往 Topic 里發(fā)幾條消息用 Console Consumer 消費出來驗證消息鏈路沒問題。最后可以手動 kill 掉 Active Controller 進程觀察 Quorum 是否在幾秒內(nèi)重新選出 LeaderBroker 是否正常重新注冊。這個過程能幫你直觀感受 KRaft 的故障恢復能力。5. 常見問題與排查技巧實錄5.1 Quorum 選不出 Leader 怎么辦這是最常見的問題表現(xiàn)是元數(shù)據(jù)操作全部超時日志里反復出現(xiàn)Election timeout和RequestVote相關(guān)記錄。第一步先確認 Quorum 節(jié)點數(shù)和存活數(shù)。三節(jié)點 Quorum 掛兩個剩下一個永遠選不出來這是數(shù)學上的必然不是 bug。用kafka-metadata-quorum.sh describe --status看當前 Quorum 里有多少節(jié)點在線。如果節(jié)點都在線但選不出來檢查網(wǎng)絡連通性。Controller 之間的通信端口默認 9093必須互通防火墻、安全組、DNS 解析都要確認。我遇到過一次三臺機器里有一臺的 hostname 解析到了錯誤的 IP導致另外兩臺收不到它的投票響應選舉一直失敗。還有一次是 MTU 不匹配小包能通大包被丟日志復制正常但選舉請求偶爾超時。5.2 日志復制卡住或延遲高如果 Follower 的matchIndex長時間不推進說明日志復制出了問題。先看 Leader 和 Follower 的磁盤 IORaft 日志寫入是 fsync 的磁盤慢會直接拖垮復制速度。用iostat看await和%util如果磁盤利用率長期 100%考慮換 SSD 或降低日志寫入頻率。另一個常見原因是日志修復循環(huán)。如果 Follower 的日志和 Leader 差異太大Leader 會不斷回退nextIndex試圖找到一致點這個過程可能很慢。如果差異超過快照范圍Leader 應該直接發(fā)快照而不是逐條回退。檢查controller.quorum.snapshot.interval.ms是否設置合理太小會導致頻繁快照太大又會導致日志修復變慢。5.3 快照生成失敗或加載慢快照生成失敗通常是因為磁盤空間不足或者狀態(tài)機太大。快照文件默認存在log.dirs下的__cluster_metadata-0目錄里如果磁盤滿了快照寫不進去日志就無法截斷最終磁盤徹底爆掉。監(jiān)控磁盤使用率預留至少 30% 的余量??煺占虞d慢一般發(fā)生在節(jié)點重啟時。如果快照文件很大加載需要時間期間節(jié)點無法參與選舉。可以調(diào)大controller.quorum.election.timeout.ms給節(jié)點更多時間完成加載。另外快照文件是二進制格式不要手動修改否則會導致狀態(tài)機不一致。5.4 動態(tài)擴縮容 Quorum 的注意事項KRaft 支持動態(tài)調(diào)整 Quorum 成員但操作必須謹慎。擴容時先把新節(jié)點格式化并啟動然后用kafka-metadata-quorum.sh add-controller把它加入 Quorum??s容時用remove-controller把節(jié)點移出。每次變更都會觸發(fā)一次聯(lián)合共識過程期間 Quorum 對多數(shù)派的要求會臨時變化必須確保網(wǎng)絡穩(wěn)定否則容易卡在中間狀態(tài)。我踩過的一個坑是縮容時一次性移除兩個節(jié)點結(jié)果剩下的節(jié)點數(shù)不滿足多數(shù)派整個 Quorum 卡死。正確做法是一次只動一個節(jié)點等 Quorum 穩(wěn)定后再動下一個。另外動態(tài)變更期間不要做其他元數(shù)據(jù)操作比如創(chuàng)建 Topic避免加重 Quorum 負擔。5.5 常見問題速查表問題現(xiàn)象可能原因排查手段解決方向元數(shù)據(jù)操作全部超時Quorum 無 Leader查看 Quorum 狀態(tài)和節(jié)點存活數(shù)恢復多數(shù)派節(jié)點檢查網(wǎng)絡選舉反復失敗網(wǎng)絡分區(qū)或時鐘漂移檢查 Controller 間連通性和 NTP修復網(wǎng)絡同步時鐘日志復制延遲高磁盤 IO 瓶頸iostat 看磁盤利用率換 SSD降低 fsync 頻率快照生成失敗磁盤空間不足df -h 看磁盤使用率清理空間或擴容節(jié)點重啟后無法加入快照加載超時看日志里的快照加載記錄調(diào)大選舉超時檢查快照文件動態(tài)擴縮容卡住多數(shù)派不滿足查看 Quorum 成員列表逐個操作確保多數(shù)派在線6. 一些實操心得與避坑建議6.1 監(jiān)控指標別只看 BrokerQuorum 才是重點很多人監(jiān)控 Kafka 只看 Broker 的請求延遲、消息吞吐、Consumer Lag但在 KRaft 模式下Quorum 的健康狀況才是元數(shù)據(jù)穩(wěn)定性的根基。必須監(jiān)控的指標包括Active Controller 是否頻繁切換、選舉耗時、日志復制延遲、快照生成耗時和大小、Quorum 節(jié)點間的網(wǎng)絡 RTT。這些指標在 Kafka 的 JMX 里都有暴露比如kafka.controller:typeKafkaController,nameActiveControllerCount和kafka.server:typeMetadataQuorum,nameCurrentLeader。我建議給 Quorum 相關(guān)指標單獨配告警。比如 Active Controller 在 5 分鐘內(nèi)切換超過 3 次或者日志復制延遲超過 500 毫秒就應該立即介入。元數(shù)據(jù)層的問題會迅速放大到整個集群等到 Broker 開始報錯再查就晚了。6.2 生產(chǎn)環(huán)境一定要 Controller 和 Broker 分離雖然 KRaft 允許一個節(jié)點同時做 Controller 和 Broker但生產(chǎn)環(huán)境強烈建議分離。原因很簡單Broker 承載消息流量磁盤 IO 和網(wǎng)絡帶寬波動大Controller 需要穩(wěn)定的低延遲環(huán)境來維持 Raft 共識?;觳繒е?Broker 的 IO 尖峰影響 Controller 的日志復制進而拖慢整個元數(shù)據(jù)層。分離部署時Controller 節(jié)點配置可以低一些比如 2 核 4G但磁盤一定要用 SSD網(wǎng)絡要低延遲。Broker 節(jié)點按消息量配置和傳統(tǒng) Kafka 集群一樣。三節(jié)點 Quorum 是最小生產(chǎn)配置五節(jié)點能容忍兩個故障適合對可用性要求更高的場景。6.3 升級和遷移要留退路從 ZooKeeper 模式遷移到 KRaft 不是一鍵操作Kafka 提供了kafka-metadata-quorum.sh和kafka-storage.sh來做遷移但過程比較復雜需要先做元數(shù)據(jù)快照再逐步切換。我的建議是先在測試環(huán)境完整走一遍遷移流程記錄每一步的耗時和可能的問題生產(chǎn)環(huán)境遷移前做好全量備份包括 ZooKeeper 數(shù)據(jù)和 Kafka 日志目錄遷移過程中保持 ZooKeeper 集群可用一旦 KRaft 出問題可以快速回滾。升級 Kafka 版本時也要注意 KRaft 的兼容性。不同版本之間的 Raft 日志格式可能有變化跨大版本升級要先看官方升級指南確認是否需要中間版本過渡。我見過一次從 3.3 直接升到 3.7結(jié)果快照格式不兼容節(jié)點起不來最后只能回滾重來。6.4 日志目錄的磁盤規(guī)劃KRaft 的元數(shù)據(jù)日志和快照都存在log.dirs下和消息數(shù)據(jù)共用磁盤。如果消息量很大元數(shù)據(jù)日志可能被擠到磁盤邊緣影響寫入性能。建議給元數(shù)據(jù)單獨掛一塊小容量 SSD通過log.dirs配置指向不同磁盤。元數(shù)據(jù)日志本身不大幾 GB 足夠但快照可能會到幾十 GB取決于 Topic 和分區(qū)的數(shù)量。另外log.dirs支持配置多個目錄Kafka 會把不同分區(qū)的日志分散到不同目錄。但元數(shù)據(jù)日志只會寫到一個目錄所以多目錄對元數(shù)據(jù)沒有幫助。如果元數(shù)據(jù)磁盤滿了整個 Quorum 會停止工作所以這個目錄的磁盤使用率要重點監(jiān)控。6.5 時鐘同步不能忽視Raft 本身不依賴絕對時鐘但選舉超時和心跳超時是基于本地時鐘的。如果節(jié)點之間時鐘漂移太大比如一臺機器慢了幾秒它可能會誤判 Leader 心跳超時頻繁發(fā)起選舉導致集群不穩(wěn)定。生產(chǎn)環(huán)境必須配 NTP 或類似的時鐘同步服務確保 Quorum 節(jié)點之間的時鐘偏差在毫秒級。我遇到過一次時鐘漂移導致的問題一臺 Controller 的時鐘比另外兩臺慢了 5 秒結(jié)果它每隔幾秒就發(fā)起一次選舉雖然選不出來但大量選舉請求占用了網(wǎng)絡和 CPU導致正常的日志復制也變慢。后來查了半天才發(fā)現(xiàn)是 NTP 服務掛了時鐘慢慢漂移了。所以時鐘同步不僅要配還要監(jiān)控。6.6 快照和日志的清理策略KRaft 會自動清理快照之前的日志但快照本身不會自動刪除。如果快照生成太頻繁磁盤上會積累大量快照文件。雖然 Kafka 會保留最近幾個快照但舊快照的清理不是實時的??梢允謩忧謇砼f快照但一定要保留最新的一個否則節(jié)點重啟時無法加載狀態(tài)。日志清理方面controller.quorum.snapshot.interval.ms控制快照頻率間接影響日志保留量。如果元數(shù)據(jù)變更頻繁比如大量 Topic 創(chuàng)建刪除可以適當調(diào)小這個值讓快照更頻繁日志截斷更及時。但快照本身也有開銷太頻繁會影響 Controller 性能。我的經(jīng)驗是元數(shù)據(jù)變更不頻繁的集群10 分鐘一次快照足夠變更頻繁的可以調(diào)到 5 分鐘。7. 從原理到實戰(zhàn)一次 Controller 頻繁切換的排查記錄7.1 問題現(xiàn)象與初步判斷某天下午監(jiān)控告警顯示 Active Controller 在 10 分鐘內(nèi)切換了 7 次。Broker 日志里大量Controller not available和Metadata update failed部分 Topic 的元數(shù)據(jù)操作超時。但消息讀寫本身沒受太大影響因為 Broker 緩存了元數(shù)據(jù)短時間內(nèi)的元數(shù)據(jù)不一致還沒波及到消息層。先看 Quorum 狀態(tài)發(fā)現(xiàn)三個 Controller 節(jié)點都在線但 Leader 頻繁易主。查看選舉日志發(fā)現(xiàn)每次選舉的任期號都在快速增長說明選舉被反復觸發(fā)。初步判斷是某個 Follower 頻繁發(fā)起選舉導致 Leader 不斷退位。7.2 深入排查網(wǎng)絡、磁盤、時鐘先查網(wǎng)絡。用ping和traceroute看 Controller 之間的延遲發(fā)現(xiàn)其中兩臺之間偶爾有 200 毫秒以上的抖動。但 Raft 的選舉超時是 1000 毫秒200 毫秒抖動不至于觸發(fā)選舉。繼續(xù)查磁盤三臺機器的磁盤 IO 都正常沒有明顯瓶頸。然后查時鐘。用ntpq -p看 NTP 同步狀態(tài)發(fā)現(xiàn)其中一臺機器的偏移量在 300 毫秒左右而且還在緩慢增大。這臺機器正是頻繁發(fā)起選舉的那個。時鐘漂移導致它的本地時間比另外兩臺快它認為 Leader 心跳超時了于是發(fā)起選舉。雖然它選不上但選舉請求會干擾 Leader導致 Leader 也誤判最終引發(fā)連鎖反應。7.3 解決與后續(xù)加固臨時解決很簡單重啟 NTP 服務強制同步時鐘。同步完成后選舉立刻停止Controller 恢復穩(wěn)定。但根本解決要加監(jiān)控對 NTP 偏移量設告警超過 100 毫秒就通知對選舉頻率設告警5 分鐘內(nèi)超過 3 次就介入。另外把controller.quorum.election.timeout.ms從 1000 毫秒調(diào)到 1500 毫秒給網(wǎng)絡抖動和時鐘偏差更多容錯空間。這個調(diào)整的代價是故障發(fā)現(xiàn)變慢但在這個場景下穩(wěn)定性比快速發(fā)現(xiàn)更重要。調(diào)整后觀察了一周沒有再出現(xiàn)頻繁選舉。7.4 這次排查給我的啟發(fā)Raft 的選舉機制對時鐘和網(wǎng)絡非常敏感而這兩個因素在分布式環(huán)境里恰恰是最容易出問題的。很多人排查 Kafka 問題習慣只看 Broker 層但在 KRaft 模式下Controller 層的穩(wěn)定性才是根基。時鐘同步、網(wǎng)絡質(zhì)量、磁盤 IO 這三樣必須納入日常巡檢。還有一點選舉超時不是越小越好。默認 1000 毫秒在大多數(shù)環(huán)境夠用但如果網(wǎng)絡質(zhì)量一般或者時鐘同步不夠精確適當調(diào)大反而更穩(wěn)。調(diào)參的本質(zhì)是在“快速發(fā)現(xiàn)故障”和“避免誤判”之間找平衡沒有標準答案只能根據(jù)實際環(huán)境試。8. 關(guān)于 KRaft 后續(xù)演進的一些觀察KRaft 目前已經(jīng)能覆蓋絕大多數(shù) ZooKeeper 模式的場景但還有一些邊界情況在完善中。比如超大規(guī)模集群下的 Quorum 性能、跨機房部署的延遲優(yōu)化、動態(tài)成員變更的平滑度這些都在持續(xù)改進。從社區(qū)動向看KRaft 是 Kafka 元數(shù)據(jù)管理的唯一方向ZooKeeper 模式遲早會退出歷史舞臺。對普通用戶來說現(xiàn)在就可以開始學習和測試 KRaft不用等到必須遷移時才臨時抱佛腳。先在測試環(huán)境搭一套跑一些元數(shù)據(jù)操作密集的場景比如批量創(chuàng)建刪除 Topic、頻繁調(diào)整分區(qū)副本觀察 Quorum 的表現(xiàn)。踩過坑之后生產(chǎn)遷移時心里就有底了。我個人在實際操作中的體會是KRaft 把 Kafka 的元數(shù)據(jù)層從“黑盒”變成了“白盒”雖然學習曲線陡了一些但一旦理解了 Raft 的日志復制和選舉機制排查元數(shù)據(jù)問題就不再是靠猜了。這套知識不僅對 Kafka 有用對其他基于 Raft 的系統(tǒng)比如 etcd、Consul也有觸類旁通的效果。