有次上線監(jiān)控告警突然炸了,Kafka 訂單 Topic 消息積壓量突破 10 萬條,下游支付服務(wù)拿不到數(shù)據(jù),部分用戶付款后一直顯示處理中。
緊急登錄集群排查,發(fā)現(xiàn)消費者組明明有 3 個節(jié)點,卻只有 1 個在正常消費,原來 10 分鐘前觸發(fā)了 Rebalance,另外兩個節(jié)點還卡在分區(qū)重新分配的狀態(tài),導(dǎo)致消費能力直接砍半。
所以我的經(jīng)驗是:Kafka出現(xiàn)消息積壓、重復(fù)、丟失這類問題,直接看是否有Rebalance,能解決大部分問題。
什么時候會觸發(fā) Rebalance?
Rebalance 本質(zhì)是消費者組內(nèi)分區(qū)與消費者的重新分配https://www.naquan.com/,只有當(dāng)消費者、分區(qū)的對應(yīng)關(guān)系被打破時才會觸發(fā),下邊咱們看看幾種比較常見的場景:
1. 消費者數(shù)量變了(最頻繁)
擴(kuò)容觸發(fā):業(yè)務(wù)高峰時加了消費者節(jié)點,比如 3 個分區(qū)原本 2 個消費者承擔(dān),新增 1 個后,需要重新分配成 1 個消費者對應(yīng) 1 個分區(qū);
下線觸發(fā):消費者節(jié)點宕機(jī)、網(wǎng)絡(luò)斷連,或進(jìn)程被誤殺,比如 3 個消費者少了 1 個,剩下 2 個要接手它的分區(qū),必然觸發(fā) Rebalance。
之前我們的日志服務(wù)就踩過坑:K8s 節(jié)點資源不足,導(dǎo)致消費者 Pod 頻繁重啟,每重啟一次就觸發(fā)一次 Rebalance,消息積壓越來越嚴(yán)重。
2. Topic 分區(qū)數(shù)加了
Kafka 不支持減少分區(qū),但新增分區(qū)時,已存在的消費者組不會自動感知新分區(qū),必須通過 Rebalance,才能把新分區(qū)分配給組內(nèi)消費者。
比如給 order-topic 從 5 個分區(qū)擴(kuò)到 8 個,原本的消費者組只會消費舊的 5 個分區(qū),直到觸發(fā) Rebalance 后,才會接手新增的 3 個分區(qū)。
3. 訂閱的 Topic 變了
消費者組通過 subscribe() 訂閱 Topic 時,若修改訂閱列表(比如從只訂閱 order-topic,改成同時訂閱 order-topic 和 pay-topic),會觸發(fā) Rebalance,重新分配所有訂閱 Topic 的分區(qū)。
4. 心跳或消費超時(隱性坑)
消費者靠心跳向 Coordinator(協(xié)調(diào)者)證明自己活著,這兩個超時參數(shù)設(shè)不好,很容易觸發(fā)誤判式 Rebalance:
心跳超時:消費者每 3 秒(默認(rèn) heartbeat.interval.ms)發(fā)一次心跳,超過 45 秒(默認(rèn) session.timeout.ms)沒發(fā),就被判定死亡;
消費超時:處理單批消息超過 5 分鐘(默認(rèn) max.poll.interval.ms),哪怕心跳正常,也會被強(qiáng)制踢出組,觸發(fā) Rebalance。
我們之前處理大訂單消息時,單條消息處理要 6 分鐘,直接觸發(fā)消費超時,導(dǎo)致 Rebalance 頻繁發(fā)生。
Rebalance 引起哪些問題
Rebalance 不是瞬間完成的,整個過程要經(jīng)歷注銷舊分區(qū)→選舉 Leader→分配新分區(qū)→消費者初始化,期間對業(yè)務(wù)的影響比你想的大。
1. 消費暫停,消息積壓
Rebalance 期間,所有消費者都會暫停消費,等待新的分區(qū)分配。如果消費者組規(guī)模大(比如 100 個消費者、1000 個分區(qū)),Rebalance 可能持續(xù)幾十秒,這段時間 Topic 消息只會堆積,下游服務(wù)拿不到數(shù)據(jù)。
所以在有消息積壓的情況,優(yōu)先看看是否有 Rebalance 的情況。
2. 消息重復(fù)和消息丟失
Rebalance 后,消費者重新拿到分區(qū)時,消費進(jìn)度可能倒退:若沒及時提交 offset(不管自動還是手動),會從最后一次提交的 offset 開始消費,中間沒提交的消息要么重復(fù)處理,要么直接跳過,也就是消息重復(fù)消費和消息丟失的原因。
極端情況(比如 Coordinator 宕機(jī)),offset 存儲的分區(qū)發(fā)生主從切換,可能導(dǎo)致 offset 數(shù)據(jù)錯亂,進(jìn)度直接回到幾天前。
3. 資源浪費,負(fù)載不均
Rebalance 要靠 Coordinator 協(xié)調(diào),頻繁觸發(fā)會占用 Kafka 集群的 CPU 和網(wǎng)絡(luò)資源;而且 Kafka 默認(rèn)的分區(qū)分配策略(Range 或 RoundRobin),很容易導(dǎo)致負(fù)載不均。
比如 5 個分區(qū)分配給 2 個消費者,可能出現(xiàn) 3 個分區(qū) vs 2 個分區(qū)的情況,其中一個消費者壓力翻倍,處理速度變慢,又會觸發(fā)新的 Rebalance,陷入惡性循環(huán)。
什么情況下會丟數(shù)據(jù)
Rebalance 本身不會直接丟數(shù)據(jù),但結(jié)合offset 提交和處理邏輯,很容易出現(xiàn)消息漏消費。
1.自動提交 offset + 消費沒完成
Kafka 默認(rèn)自動提交 offset,提交時機(jī)是 poll 到消息后,等 5 秒(默認(rèn) auto.commit.interval.ms)自動提交。如果剛提交完 offset,消息還沒處理完就觸發(fā) Rebalance,新消費者會從已提交的 offset 之后 開始消費,中間沒處理的消息就丟了。
舉個例子:
消費者 A poll 到 offset 100-200 的消息,5 秒后自動提交 offset 200;
處理到 150 條時,節(jié)點突然宕機(jī),觸發(fā) Rebalance;
新消費者 B 從 offset 200 開始消費,offset 150-199 的消息直接丟失。
2. 手動提交 offset 時機(jī)錯了
手動提交時,如果把提交 offset 放在處理消息之前,也會丟數(shù)據(jù)。
錯誤邏輯:先提交 offset → 再處理消息;
風(fēng)險:提交后、處理前觸發(fā) Rebalance,新消費者會跳過已提交的消息,導(dǎo)致未處理的消息丟失。
正確的做法應(yīng)該是先處理消息→再提交 offset,確保消息處理完才更新進(jìn)度。
什么情況下會重復(fù)消費?
相比丟數(shù)據(jù),kafka Rebalance 導(dǎo)致的重復(fù)消費更普遍,核心原因都是 offset 提交滯后于消息處理。
1. 手動提交時,Rebalance 打斷了提交
開啟手動提交后,若在處理完消息→提交 offset 的間隙觸發(fā) Rebalance,offset 沒提交成功,新消費者會從上次提交的位置重新消費。
消費者 A 處理完 offset 100-200 的消息,準(zhǔn)備提交時,因心跳超時被踢出組;
新消費者 B 從 offset 100 開始消費,導(dǎo)致 100-200 的消息被重復(fù)處理。
2. 消費超時被踢,消息還在處理
處理消息耗時超過 max.poll.interval.ms,消費者被判定死亡,但實際還在處理消息。
消費者 A 處理大消息用了 6 分鐘,超過默認(rèn) 5 分鐘的超時時間,被踢出組;
新消費者 B 接手分區(qū),從上次提交的 offset 開始消費;
消費者 A 后來處理完消息,想提交 offset 卻發(fā)現(xiàn)自己已被踢出,提交失敗,導(dǎo)致消息重復(fù)。
3. offset 找不到,回退到最早
如果消費者組的 auto.offset.reset 設(shè)為 earliest(默認(rèn)是 latest),Rebalance 后找不到已提交的 offset(比如 offset 數(shù)據(jù)損壞),會從 Topic 最早的消息開始消費,導(dǎo)致歷史消息重復(fù)。
如何優(yōu)化 Rebalance
Rebalance 這種情況是無法完全避免,不過我們可以來優(yōu)化,能把影響降到最低。
1. 避免頻繁觸發(fā) Rebalance
調(diào)優(yōu)超時參數(shù):根據(jù)消息處理耗時,把 max.poll.interval.ms 設(shè)大(比如大消息設(shè)為 10 分鐘),session.timeout.ms 設(shè)為 60-120 秒,避免誤判死亡;
保證消費者穩(wěn)定:用監(jiān)控盯緊消費者節(jié)點的 CPU、內(nèi)存,避免 K8s Pod 頻繁重啟,或服務(wù)器宕機(jī)。
2. 安全處理 offset 提交
優(yōu)先手動提交,關(guān)閉自動提交(enable.auto.commit=false),在消息處理完成后再調(diào)用 commitSync() 提交;
必要時用事務(wù),如果業(yè)務(wù)不允許重復(fù)消費,結(jié)合 Kafka 事務(wù),確保消息處理 和 offset 提交原子性。
3. 優(yōu)化分區(qū)分配
選粘性分配策略:把 partition.assignment.strategy 設(shè)為 StickyAssignor,Rebalance 時盡量保留原有分配,減少分區(qū)變動。
4. 優(yōu)化消費邏輯
做好冪等性:比如用訂單 ID 作為唯一鍵,即使重復(fù)消費,也不會導(dǎo)致業(yè)務(wù)邏輯出錯(比如重復(fù)扣錢、重復(fù)生成訂單)。
寫在最后
Rebalance 是面試的時候常愛問的場景題,它是 Kafka 消費者組的雙刃劍,用好能均衡負(fù)載,用不好就會引發(fā)故障,最后我總結(jié)下:
觸發(fā) Rebalance 主要是消費者或分區(qū)變了或超時了;
丟數(shù)據(jù)和重復(fù)消費,本質(zhì)是 offset 提交和 Rebalance 時機(jī)沒配合好;
優(yōu)化超時參數(shù)、手動提交 offset、做好冪等性,是減少影響的關(guān)鍵。