开放问答
RocketMQ 消费端「commit 落库 → ack 消息」之间的崩溃窗口,如何把重复消费率压到 0.01% 以下
该问题的正文、回答与评论。
提问者 江墨白 · 状态 open
RocketMQ幂等重复消费消息中间件分布式一致性
【背景】
我们用 RocketMQ 4.9.7(3 broker 集群)承载「订单创建 → 扣库存 + 发通知」的异步链路,topic order-created 共 32 queue,消费组 8 实例(1:4 部署),日均约 2000 万条消息。消费端逻辑固定为三步:先写 MySQL 8.0(一主两从,半同步 rpl_semi_sync_master_wait_for_slave=1,timeout=2000ms)的业务表 + 幂等表(唯一键 msgId),commit 成功后再调 consumer.ack。
【现象】 过去 3 个月线上出现 4 次重复消费,逐条复盘:
- 2 次是 JVM 被 OOM killer 杀掉(堆 4G,下游超时堆积 + GC 抖动),commit 已落库、ack 未发;
- 1 次是半同步超时降级为异步(
rpl_semi_sync_master_timeout=2000ms触发)后主库宕机,binlog 丢了约 1.2s 的写入,幂等表跟着丢; - 1 次是网络抖动导致 ack 包在 broker 侧超时被丢弃(broker 端
consumerOffset回退了 3 条)。 实测重复率约 0.3%(日均 2000 万条里 6 万条左右),业务侧靠「订单号 + 操作类型」唯一键挡掉了 95% 的重复,剩下 5% 是「幂等表本身没写进去」的边界情况,最终落到人工。
【已尝试的排查与结果】
- 把消费线程池从 20 降到 8,GC 停顿从 P99 480ms 降到 P99 120ms,OOM 次数从 2 次/季度降到 1 次/季度;
- 半同步改成
rpl_semi_sync_master_wait_for_slave=2(要求 2 个从确认),压测发现 P99 写延迟从 8ms 涨到 22ms,业务侧不同意上线; - 幂等表从 InnoDB 表改成 Redis(
SETNX + TTL 7d),挡住了 95% 的重复,但 Redis 是主从非集群,主从切换窗口实测 1.8s,等于把问题搬了个家; - 死信队列 + 人工介入:
maxReconsumeTimes=16之后进%DLQ%,每周对账一次——但 6 万条/天的重复量,人工根本对不过来。
【期望得到的答案形态】
- 量化:「commit 落库 → ack 发出」这个崩溃窗口,在 2000 万条/天、单条处理 P99 约 120ms 的量级下,理论重复率大概是多少?有没有可复用的公式或经验值(比如 崩溃概率 × 单条处理时长 / 平均间隔)?
- 幂等键选型:幂等键用
msgId还是业务唯一键(订单号 + 操作类型)?两者在「broker 重投了不同 msgId 但业务语义相同」的场景下,哪个更稳?还是必须两者都建唯一键? - 对账粒度与频率:对账任务是「按 msgId 全量对账」还是「按业务唯一键增量对账」?频率 5min / 1h / 1d 各有什么 trade-off?增量对账的「水位线」放在哪(consumerOffset?业务表
gmt_modified?) - 组合拳:有没有团队真正把「重复消费率 < 0.01%」做到过的?用的是哪套组合(幂等表 + 对账 + 死信 + 人工 + 业务侧幂等)?各自承担多少比例的兜底?
- 半同步降级感知:消费端能不能感知「主库正在半同步降级为异步」?比如通过
SHOW SLAVE STATUS的Seconds_Behind_Master+Rpl_Semi_Sync_Master_status联合判断,降级窗口内主动降级到「只写业务表、不写幂等表」+ 告警?
评论
暂无评论。