K8s 滚动发布时消费者优雅停机,in-flight 消息如何做到零丢失
该问题的正文、回答与评论。
【背景】
K8s 1.29 + Kafka 3.5(3 broker,ISR=3),消费组 12 实例(K8s Deployment,maxSurge=1 / maxUnavailable=0),topic order-pay 32 分区,日均约 4500 万条,max.poll.records=500,业务逻辑是「读 batch → 逐条写 MySQL → 整批 commit offset」。滚动发布时 K8s 先发 SIGTERM,terminationGracePeriodSeconds=30。
【现象】
滚动发布窗口内偶发丢消息(统计下来大约每 50 次发布丢 1~2 次,涉及 100~300 条)。排查结论:Kafka consumer 收到 SIGTERM 后我们调了 consumer.wakeup() + consumer.close(),但此时 poll() 已经返回了一批 500 条消息,业务线程只处理了约 200 条就因 30s 超时被 SIGKILL 强杀——剩余 300 条既没 commit offset,也没写库,直接蒸发。
【已尝试】
- 把
max.poll.records从 500 降到 100,降低单次 in-flight 量——减少丢消息条数,但没根治; - 在 SIGTERM handler 里加 5s sleep 等 in-flight 跑完——偶发仍超时;
- 把
terminationGracePeriodSeconds从 30s 提到 120s——发布窗口拉长到 3 分钟,业务方不接受。
【期望得到的答案形态】
想听大家生产环境里消费者优雅停机的具体实践,尤其关注三点:
(a) SIGTERM 之后,如何可靠地判断 in-flight 消息已全部处理完(或安全回滚)再 close?有没有比"sleep 等"更优雅的机制(比如 in-flight 计数器 / 状态机)?
(b) 是否应该把「读 batch → 逐条处理 → 整批 commit」改成「处理完一条 → 立即 commit → 再处理下一条」的同步模式?代价是什么(吞吐下降多少)?
(c) K8s 侧的 preStop hook、readinessProbe 在消费者停机流程里分别能扮演什么角色?有没有人用 preStop 做"先摘流量再等 in-flight 跑完"的?
附上我们当前的 SIGTERM handler 伪代码(方便大家直接指出问题):
onSIGTERM() {
consumer.wakeup(); // 让 poll() 提前返回
sleep(5s); // 等 in-flight 跑完(不靠谱)
consumer.close(5s); // 超时直接抛
}
求拍砖,也欢迎贴你们的生产配置和踩坑记录。