快到年底了,系統(tǒng)開始頻繁出問題。
我有一個(gè)非常合理、非常工程師的懷疑:
老板不想發(fā)年終獎(jiǎng),所以系統(tǒng)開始搞事。
果不其然—— 幾年都遇不到一次的 Kafka 消息積壓, 在一個(gè)本該安靜下班的夜晚, 卷土重來了。
今晚,注定是個(gè)不眠夜。
原神啟動(dòng)之前,我先啟動(dòng)了 Kafka 面板。
-****01-
**事故現(xiàn)場(chǎng) **
事情是這樣的。
我剛下班,正準(zhǔn)備洗洗睡,
組里的小伙伴突然沖過來,語氣已經(jīng)帶點(diǎn)顫:
“Kafka 消息積壓一直在漲,預(yù)覽圖全出不來!”
我點(diǎn)開面板一看,血壓直接上來了:
原來 4 個(gè)分區(qū)
已積壓 1200+
新加的分區(qū)
也開始積壓
而且 速度越來越快
第一反應(yīng)非常自然,也非?!靶率钟押谩保?/p>
是不是消費(fèi)者慢?那我多加點(diǎn)實(shí)例不就完了?
于是我:
加 Pod
消費(fèi)能跑
然后……
越跑越卡 Pod 開始一個(gè)接一個(gè)掛
這時(shí),我的困意 和不祥的預(yù)感 同時(shí)達(dá)到了頂峰。
[圖片上傳失敗...(image-6a4696-1767778965506)]
-****02-
**第一層誤判 **
我突然想起一件事:
Spring Cloud Stream 好像支持并發(fā)消費(fèi)?
于是讓開發(fā)老哥把:
<pre data-start="866" data-end="893" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important;">
concurrency:10
</pre>
一把拉滿。
結(jié)果呢?
這時(shí)候我才反應(yīng)過來一個(gè)致命誤解:
****concurrency ≠ 并行處理一條消息****
而是:
我人當(dāng)場(chǎng)清醒了。
concurrency = 消費(fèi)者線程數(shù)
一個(gè)線程 ≈ 一個(gè)分區(qū)
分區(qū)本來就不均勻
一加線程 流量?jī)A斜直接拉滿
消息 積壓更快
Pod 直接被打爆
CPU、內(nèi)存 一起起飛
[圖片上傳失敗...(image-a21ac6-1767778965506)]
-****03-
詭異現(xiàn)象
我把所有 Pod 日志全拉下來,一條一條看。
結(jié)果非常魔幻。
監(jiān)聽器日志顯示:
全部執(zhí)行成功
但與此同時(shí):
Kafka 報(bào) 消費(fèi)超時(shí)
面板顯示 Consumer Group 頻繁 Rebalance
我當(dāng)場(chǎng)愣住。
成功了,又超時(shí)?
這是什么?
薛定諤的 Kafka 消費(fèi)?
但作為一個(gè)堅(jiān)定的唯物主義程序員, 我選擇繼續(xù)往下挖。
[圖片上傳失敗...(image-f454af-1767778965505)]
-****04-****破案關(guān)鍵
答案,藏在 Kafka 的消費(fèi)模型里。
你以為的 Kafka 是這樣的:
來一條 → 消費(fèi)一條 → 確認(rèn)一條
但實(shí)際上,Kafka 是這樣的:
一次 poll 一批 → 全部處理完 → 才提交 offset
而 Spring Cloud Stream,為了“好用”, 干了一件非常容易坑人的事:
底層是批量拉取,但監(jiān)聽器只給你一條
我們假設(shè)一個(gè)真實(shí)到不能再真實(shí)的配置:
max.poll.records = 500單條消息處理:10s
處理方式:串行
消費(fèi)超時(shí)時(shí)間:300s
那會(huì)發(fā)生什么?
500 × 10s = 5000s
也就是說:
一次 poll
最多只能處理 30 條
后面的消息根本來不及
于是就出現(xiàn)了那一幕:
單條邏輯: 成功
整批消費(fèi): 超時(shí)
Kafka 認(rèn)為你“失聯(lián)”
觸發(fā) Consumer Rebalance
offset 不提交
后面的消息 全部堵死
我當(dāng)場(chǎng)只想說一句:
我咧個(gè)豆,案子破了。
[圖片上傳失敗...(image-2a52d2-1767778965503)]
-****05-****兩種解決方案
方案一:立刻止血(適合半夜)
<pre data-start="1963" data-end="1991" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important;">
ack-mode:RECORD
</pre>
效果:
每條消息處理完立刻提交
不再被“批次”拖死
改一行就能下班睡覺
代價(jià):
吞吐量下降
Kafka 的優(yōu)勢(shì)用不滿
適合場(chǎng)景:
救火 保命 保年終獎(jiǎng)
方案二:批量 + 并行(推薦)
思路:
批量要小,并行要真
1. 控制批量大小
max.poll.records:50
2. 自己并行處理這一批
@StreamListener("<TOPIC>")
效果:
批次不大,不超時(shí)
真正并行,吞吐拉滿
offset 提交穩(wěn)定
Kafka 安靜了,世界也安靜了
[圖片上傳失敗...(image-44c922-1767778965502)]
-****06-****總結(jié)
這次事故教會(huì)我的三件事
1. Kafka 慢,80% 不是 Kafka 的鍋
而是你 消費(fèi)模型 + 超時(shí)配置 + 批量大小 從來沒想清楚。
2. Spring Cloud Stream 很友好
但:
越像“隊(duì)列”的封裝,越容易誤導(dǎo)你
3. 半夜事故,拼的不是手速
而是你 對(duì)底層機(jī)制的理解深度
那天問題解決的時(shí)候,已經(jīng)快天亮了。
咖啡喝完了
Kafka 面板綠了
飛書安靜了
我終于能安心睡覺了。
如果你也遇到過:
Kafka 消息積壓
日志成功但一直超時(shí)
Consumer Rebalance 地獄循環(huán)
希望這篇文章, 能幫你 少熬一次夜。