沉默是金,總會發光

大家好,我是沉默




快到年底了,系統開始頻繁出問題。

我有正當理由懷疑:
老闆不想發年終獎,所以開始搞事。

這不,幾年都遇不到一次的 Kafka 消息積壓


在一個本該安靜下班的夜晚,捲土重來了


今晚註定是個不眠夜。

原神啓動之前,我先啓動了 Kafka 面板。



-01-

事故現場 

事情是這樣的。

我剛下班,正準備洗洗睡,
組裏的小夥伴突然火急火燎地衝過來:

“Kafka 消息積壓一直在漲,預覽圖全出不來!”


我上去一看:

  • 原來的 4 個分區
    已積壓 1200+ 條
  • 新加的分區:
    也開始積壓,而且速度越來越快


第一反應很自然:

是不是消費者處理太慢?那我多加幾個實例不就完了?


於是:

  • 加 Pod
  • 消費能跑
  • 然後……越跑越卡
  • 再然後……Pod 開始掛


這時,我的睏意和不祥預感同時達到了頂峯。


同事離職了,Kafka 消息積壓怎麼辦?_List



-02-

第一層誤判 

我突然想起一件事:

Spring Cloud Stream 好像支持併發消費?

於是讓開發老哥把 concurrency 改成 10。


結果呢?

  • 消息 積壓更快
  • Pod 直接被打爆
  • CPU、內存一起飆


這時候才反應過來:

concurrency ≠ 並行處理一條消息

而是:

  • concurrency = 消費者線程數
  • 一個線程 = 負責一個分區
  • 分區本來就不均勻
  • 一加線程,流量傾斜直接拉滿



同事離職了,Kafka 消息積壓怎麼辦?_Pod_02



-03-

詭異現象

我把所有 Pod 日誌拉下來,一條條看。

結果非常魔幻:

  • 監聽器日誌:
    全部執行成功
  • 但同時又出現:
    消費超時
  • Kafka 面板裏:
    Consumer Group 頻繁 Rebalance


我當場愣住。

成功了,又超時?
這是什麼薛定諤的消費?


但作為一個堅定的唯物主義者,我選擇繼續查。


同事離職了,Kafka 消息積壓怎麼辦?_List_03



-04-

破案關鍵

問題的答案,藏在 Kafka 的消費模型裏

你以為的 Kafka:

來一條 → 消費一條 → 確認一條

實際上的 Kafka:

消費者主動一次拉一批 → 處理完 → 才提交 offset


而 Spring Cloud Stream,為了“好用”,幹了件非常容易坑人類的事:

批量拉取,但監聽器只給你一條


假設:

  • max.poll.records = 500
  • 每條消息處理 10s
  • 處理方式是 串行
  • 消費超時時間:300s


那會發生什麼?

500 × 10s = 5000s

一次 poll,最多隻能處理 30 條

於是就出現了詭異現象:

  • 單條邏輯:成功
  • 整批消費:超時
  • Kafka 認為你“失聯”
  • 觸發 Consumer Rebalance
  • offset 不提交
  • 後面的消息全堵死


我咧個豆。

案子破了。


同事離職了,Kafka 消息積壓怎麼辦?_Pod_04


-05-

兩種解決方案

方案一:立刻止血(適合半夜)

ack-mode: RECORD

效果:

  • 每條消息處理完立刻提交
  • 不再被批次拖死
  • 改一行就能下班睡覺

代價:

  • 吞吐量下降
  • Kafka 的優勢用不滿

適合:救火、保命、保年終獎

方案二:批量 + 並行(推薦)

思路只有一句話:

批量要小,並行要真

1. 控制批量大小

max.poll.records: 50

2. 自己並行處理這批消息

@StreamListener("<TOPIC>")
public void consume(List<byte[]> payloads) {
    List<CompletableFuture<Void>> futures =
        payloads.stream().map(bytes -> {
            Payload payload =
                JacksonSnakeCaseUtils.parseJson(
                    new String(bytes), Payload.class
                );
            return CompletableFuture.runAsync(() -> {
                // 業務處理
            }, batchConsumeExecutor).exceptionally(e -> {
                log.error("Thread error {}", bytes, e);
                return null;
            });
        }).collect(Collectors.toList());
    // 等待整批完成,再統一提交 offset
    CompletableFuture.allOf(
        futures.toArray(new CompletableFuture[0])
    ).join();
}

效果:

  • 批次不大,不超時
  • 真正並行,吞吐拉滿
  • offset 提交穩定
  • Kafka 安靜了,世界也安靜了


同事離職了,Kafka 消息積壓怎麼辦?_Pod_05


-06-

總結

這次事故,真正教會我的三件事

1️⃣ Kafka 慢,80% 不是 Kafka 的鍋

是你 消費模型 + 超時配置 + 批量大小 沒想清楚

2️⃣ Spring Cloud Stream 很友好

越是“像隊列”的封裝,越容易誤導你

3️⃣ 半夜事故,拼的不是手速

而是你對底層機制的理解深度


程序員的深夜,不該白熬

那天問題解決的時候,已經快天亮了。

咖啡喝完了,
Kafka 面板綠了,
飛書安靜了,
我終於能安心睡覺了。


如果你也遇到過:

  • Kafka 積壓
  • 日誌成功但超時
  • Consumer Rebalance 地獄循環


希望這篇文章,能幫你少熬一次夜。



同事離職了,Kafka 消息積壓怎麼辦?_List_06




-07-

粉絲福利

我這裏創建一個程序員成長&副業交流羣,
和一羣志同道合的小夥伴,一起聚焦自身發展,
可以聊:
技術成長與職業規劃,分享路線圖、面試經驗和效率工具,
探討多種副業變現路徑,從寫作課程到私活接單,
主題活動、打卡挑戰和項目組隊,讓志同道合的夥伴互幫互助、共同進步。
如果你對這個特別的羣,感興趣的,
可以加一下,微信通過後會拉你入羣,
但是任何人在羣裏打任何廣告,都會被我T掉。