已采纳
回答正文
回复
讨论正文
该问题的正文与回答。
背景
我是 8 年技术团队负责人,日常跟分布式一致性和消息中间件打交道。我们订单 → 履约链路用的是「本地消息表 + Kafka 事务 producer」组合:producer 端 enable.idempotence=true + transactional.id=fulfill-{shard},消费者组 isolation.level=read_committed。生产端流程:
BEGIN → INSERT order / UPDATE inventory / INSERT msg_outbox → COMMIT(DB 侧原子提交);beginTransaction() → send() × N → commitTransaction();msg_outbox,对超过 10 分钟未确认的条目重新 send 一次。现象(卡点)
有一次发布时 producer 进程在 commitTransaction() 网络半途中被 kill -9。DB 侧第 1 步的 COMMIT 已经成功(order + msg_outbox 都落了),但 Kafka 侧事务被标记为 aborted——read_committed 消费端永远看不到这批消息(LSO 不推进)。兜底靠扫描器 30s 内重新 send 一批新的消息(新 record key、新 partition offset)投递成功。
问题就出在这里:同一个业务事件,被投递了两次——一次来自 aborted 的旧事务(理论上不可见,但 broker 日志里还残留着 record),一次来自扫描器的 rescan。如果消费端幂等做得好,只是「多发一条」;如果消费端有非幂等副作用(比如调支付 API、写外部系统),就是真实资损。
已尝试的排查
kafka-consumer-groups --describe:消费组 lag 在 aborted 窗口内持续 0(因为 read_committed 看不到),对账脚本按「DB 已提交、下游未收到」做 diff,命中 27 条,全部是那次 kill -9 的批次。kafka-metadata --list-transactions:看到那批事务 state=Completed(其实是 aborted,metadata 里不区分 aborted/committed 的终态,只能看 commit 时间戳 vs expire 时间戳),没有直接的「aborted 原因」字段。msg_id + biz_key 唯一键)挡住了重复,但幂等表本身在 rescan 窗口内是空的——因为 rescan 的 msg_id 是新生成的,和旧事务的 msg_id 不同。期望得到的答案形态
msg_outbox 的 sent_at - created_at 分布、还是直接看消费端幂等表的重复率、还是 broker 端 __transaction_log 的 aborted 事件?有没有具体的指标 / 告警阈值可以借鉴?__transaction_log 轮询、或者把「扫描决策」本身放进另一个事务里?引用
欢迎贴你们线上的真实数字和机制,越具体越好。