开放问答
Kafka 事务提交 offset 后、下游 DB 提交前进程崩溃,如何设计不丢不重的兜底方案
该问题的正文、回答与评论。
提问者 江望舒 · 状态 open
Kafka消息中间件事务一致性exactly-onceMySQL
【背景】 我们有一条对账链路:Kafka 3.6(3 节点,topic 12 分区)→ 消费者组 6 实例 → MySQL 8.0(单主+1 从,半同步)。消费逻辑:读消息 → 写 MySQL 对账表(REQUIRES_NEW)→ 调用 sendOffsetsToTransaction 把 offset 放进同一个 Kafka 事务 → 事务 commit 后继续。消息量约 800 万条/天,单条处理 P99 约 40ms。
【现象】 上个月两次线上事故:Pod 被 OOMKilled(exit 137)时,日志显示 Kafka 事务已 commit(offset 已推进),但 MySQL 那边该批 3~7 条消息的 INSERT 实际没落盘(从库回查无记录,主库 binlog 里也查不到对应事务)。结果就是这批消息被永久跳过——既不重放(offset 已提交)也没入库,对账出现缺口。
【已尝试的排查与结果】
- 怀疑是 JDBC 连接池在 commit 前被回收:抓了 HikariCP 日志,确认是「INSERT 成功返回 → 事务 commit → 随后 sendOffsetsToTransaction 成功」,顺序没错;真正问题是我把 MySQL 事务 commit 放在了 sendOffsetsToTransaction 之后,依赖「DB commit 失败则不提交 Kafka offset」——但 OOMKill 是 SIGKILL,DB 连接在 commit 后、Kafka commit 前被内核直接杀掉时,DB 侧 commit 其实已持久化,反方向(DB 没落盘但 Kafka offset 已推进)发生在 DB 事务提交走半同步、从库未 ACK 就宕机回滚的场景。
- 加了业务表唯一键 + INSERT IGNORE 防重,但解决不了「漏」的问题。
- 查了 Kafka 文档:sendOffsetsToTransaction 的语义是 offset 随事务原子提交,但没有覆盖「事务外副作用」的窗口。
【期望得到的答案形态】
- 一个明确的时序图/伪代码:DB 写入、DB commit、Kafka offset commit 三者的正确先后顺序,以及每步失败后的补偿路径;
- 业界对「事务内副作用 vs 事务外副作用」的标准做法(比如 outbox 模式、两阶段写、或干脆放弃 exactly-once 改幂等消费),最好带具体代码片段或配置;
- 半同步 MySQL 下「commit 返回成功但数据可能回滚」的边界条件(rpl_semi_sync_master_wait_for_slave_count、timeout 参数)如何规避;
- 可观测性:怎么监控这个缝隙(比如对账表条数 vs Kafka offset 推进量的 diff 告警)的具体 PromQL 或 SQL。
评论
暂无评论。