D
MQ中级30 分钟

消息只被消费一次

MQ 至少一次投递不可避免,业务要靠幂等表做到效果只发生一次。

设计一致性MQ至少一次投递ACK 丢失幂等表业务副作用去重键
Java 场景 PDF Ch39 · MQ 消息只被消费一次
所属路线 ·MQ 异步系统
01

面试官问题

真实问法

MQ 怎么保证消息只被消费一次?如果 Consumer 业务处理成功了,但 ACK 丢了,Broker 又重投,怎么保证不会重复扣款或重复发货?

这题考什么

考 MQ 投递语义、ACK 丢失导致重复投递、消费端幂等、去重表设计,以及业务副作用的边界。

正确建模思路

先承认 MQ 通常只能保证至少一次投递,不能从 Broker 侧承诺业务效果只发生一次。正确性放在 Consumer:用业务唯一键写幂等表,未处理才执行业务副作用,已处理直接 ack。

02

业务背景

支付、发货、积分发放这类消费逻辑都有不可重复的业务副作用;MQ 重投时,系统必须保证副作用只生效一次。

学完后你能回答
  • 为什么 MQ 不能直接承诺业务 exactly once?
  • ACK 丢失后为什么会重复投递?
  • 幂等表如何保证副作用只发生一次?
03

关键约束

  • Broker 重投不可完全避免。
  • 业务副作用不能重复执行。
  • 幂等判断要和业务更新保持事务一致。
  • 重试窗口内要能识别已处理消息。
04

系统建模

  1. 1

    Broker:至少一次投递消息。

  2. 2

    ACK 信号:可能丢失或超时。

  3. 3

    Consumer:先做幂等判断,再做业务副作用。

  4. 4

    幂等表:用业务唯一键记录处理结果。

  5. 5

    副作用计数器:展示扣款/发货只能增加一次。

05

动画推演

动画看什么

看 ACK 信号在网络中丢失后 Broker 如何重投;Consumer 第二次收到消息先查幂等表,发现已处理后跳过业务副作用。

  1. 01 Broker 投递消息。
  2. 02 Consumer 处理成功但 ACK 丢失。
  3. 03 Broker 重试导致重复投递。
  4. 04 Consumer 查询幂等表。
  5. 05 已处理则跳过业务副作用。
  6. 06 未处理则执行业务并写入幂等表。
视觉元素
消息卡片投递次数ACK 信号幂等表业务副作用计数器

观察 ACK 丢失、Broker 重投、幂等表命中与业务副作用计数的变化。

MQ 幂等实验台
消息
MSG-PAY-202405-7788
businessKey · PAY:ORD-8891
投递次数 · 1
幂等表
Broker
业务副作用
0
order · WAIT_PAY
收到消息
timeline4 events · 4 frames
MQ 通常是至少一次 · 1/4
06

故障注入 / 错误做法

错误回答

只要消费成功后 ACK 就行,MQ 会保证消息只投一次;或者用 synchronized 防止同一时刻重复消费。

正确回答

先承认 MQ 通常只能保证至少一次投递,不能从 Broker 侧承诺业务效果只发生一次。正确性放在 Consumer:用业务唯一键写幂等表,未处理才执行业务副作用,已处理直接 ack。

故障注入

错误做法是每次收到消息都直接扣款。动画会让 ACK 丢失触发二次投递,展示没有幂等表时副作用计数器如何变成 2。

风险点
  • ACK 丢失或消费超时会触发重复投递。
  • 先执行业务再写幂等表,宕机后可能重复副作用。
  • 幂等键选错会把不同业务请求误判为重复。
  • 幂等表和业务表不在同一事务会出现中间态。
07

30 秒面试表达

  1. 01

    我不会说 MQ 天然保证只消费一次,主流 MQ 更现实的是至少一次投递,所以重复投递一定要按会发生来设计。

  2. 02

    Consumer 收到消息后,用业务唯一键先查幂等表;已处理就直接 ack,不再执行业务副作用。

  3. 03

    未处理时,把写幂等记录和业务更新放在同一个本地事务里,事务成功后再 ack。

  4. 04

    这样即使 ACK 丢了 Broker 重投,第二次也只会命中幂等表并跳过业务,达到业务效果只发生一次。

08

常见追问

幂等表应该用什么 key?

用业务唯一键,比如订单号+操作类型、支付流水号、消息业务 id,而不是 MQ 的投递次数或临时 messageId。

先写幂等表还是先执行业务?

要放在同一事务里。常见做法是插入幂等记录作为第一步,唯一键冲突说明处理过;随后执行业务更新,事务一起提交。

幂等表会不会很大?

会,所以要按业务保留期分区或归档。幂等记录只保留能覆盖最大重试窗口和审计需要的时间。

09

复盘卡片

  • MQ 的至少一次投递是事实,不是异常。
  • 业务 exactly once = 消费端幂等 + 本地事务。
  • 幂等键必须来自业务,不要依赖投递次数。