高并发 · 消息队列 · 投递语义
MQ 的 QoS 投递语义,一次讲清
最多一次、至少一次、精确一次——到底差在哪?为什么会有丢失和重复?
下面用可交互的故障模拟让你亲眼看到消息的去向。
1 先搞懂:QoS 到底在保什么 / What is guaranteed
MQ 在网络里跑,故障是常态:网络抖一下、Broker 宕机、消费者崩溃……消息随时可能「没送到位」。QoS(服务质量 / 投递语义)要回答一个问题:
一条消息,最终被消费者「成功处理」几次? 答案只可能是三种之一:0 次(丢了)、1 次、多于 1 次(重复了)。三种 QoS 就是在这三个数字上做取舍。
记住这张图的对应关系:
最多一次 At-Most-Once 允许落在 0(可能丢),绝不会到 ≥2(绝不重复)。
至少一次 At-Least-Once 允许落在 ≥2(可能重复),绝不会到 0(绝不丢)。
精确一次 Exactly-Once 永远钉在 1,既不丢也不重复。
2 三种语义,点开看细节 / The three levels
点击任意一张卡片展开:它的机制、代价和适用场景。
最多一次
At-Most-Once · 可能丢失,绝不重复
- 机制:发完就当成功,不重试;消费者先提交位移、后处理。
- 代价:任何环节故障 → 消息静默丢失,你根本不知道。
- 适用:日志、指标、可容忍丢数据的埋点;丢了不影响业务。
- 追求:低延迟、高吞吐,宁愿丢也不想重。
至少一次
At-Least-Once · 绝不丢失,可能重复
- 机制:发送方超时重试直到确认;消费者先处理、后提交位移。
- 代价:故障下同一条消息被投递多次 → 业务需自己幂等。
- 适用:绝大多数业务(订单、扣款前置),配合幂等最稳。
- 现状:Kafka / RocketMQ 默认就是至少一次。
精确一次
Exactly-Once · 不丢不重,正好一次
- 机制:幂等生产者 + 事务 + 幂等消费,三件套。
- 代价:实现最复杂、性能开销最大,并非"免费"。
- 适用:钱相关、不能重复也不能丢(转账、计费)。
- 真相:主流是"至少一次 + 幂等",做不到理论真·一次。
3 一切差异,源于两个「时机」 / The root cause
为什么三种语义表现不同?拆开看,整条链路只有两个决策点决定消息会不会丢 / 重:
决策点 ①:生产者发失败,重试吗?
• 不重试 → 可能丢(最多一次)
• 重试 → 可能重(至少一次)
• 重试 + 去重 → 正好(精确一次)
决策点 ②:消费者先提交位移,还是先处理?
• 先提交后处理 → 崩了就丢(最多一次)
• 先处理后提交 → 崩了重投就重(至少一次)
• 处理与提交原子化 → 正好(精确一次)
一句话:「丢」来自不重试 / 早提交;「重」来自重试 / 晚提交。精确一次就是用幂等 + 事务把"重"消灭掉,同时保住"不丢"。
4 交互模拟器:亲眼看到消息去哪了 / Live simulator
左边选 QoS 模式 和 故障场景,点「运行模拟」,看消息是丢失、重复还是正好一次。
① 选择 QoS 模式
② 选择故障场景
就绪——选择模式与故障后点击运行
成功处理
故障点
重复副本
怎么读这个模拟:无论选哪种 QoS,无故障时结果都是"正好一次"——差异只在有故障/重试时才显现。重点对比:同样一次「消费者崩溃」,最多一次会丢、至少一次会重、精确一次仍正好。这就是三者的本质区别。
5 精确一次,到底怎么实现的 / How exactly-once works
精确一次几乎从不靠"魔法",而是用至少一次打底 + 幂等去重把重复消掉。三件套:
① 幂等生产者
Producer ID + 序列号
- Broker 给每个生产者发 PID,每条消息带序列号。
- 重复副本到达时,Broker 比对序列号直接丢弃。
- 消灭"发送重试导致的重复"。
② 事务消息
发送 + 存储 原子提交
- 要么消息成功落盘并提交,要么整体回滚。
- Broker 宕机不产出"半成品"。
- 消灭"存储阶段的中断"。
③ 幂等消费
业务层去重兜底
- 用唯一键(订单号等)判重:处理过就跳过。
- 处理与提交位移放同一事务。
- 消灭"重复投递造成的重复处理"。
精确一次 ≈ 至少一次 + 幂等去重 + 事务
别被名字骗了: 真正的端到端"恰好一次"在分布式里极难(甚至不可能)严格保证。Kafka 的 exactly-once 是局限性下的最优解:它保证"在 Kafka 内部 + 幂等消费"范围内不丢不重,但跨系统(如消费后写 MySQL 又崩)仍需业务幂等兜底。
6 怎么选:一张表 + 口诀 / Choosing
| 维度 | 最多一次 | 至少一次 | 精确一次 |
| 会丢失吗 | 会 ❌ | 不会 ✅ | 不会 ✅ |
| 会重复吗 | 不会 ✅ | 会 ⚠️ | 不会 ✅ |
| 复杂度 | 最低 | 中(需幂等) | 最高 |
| 性能 | 最好 | 较好 | 最差 |
| 典型场景 | 日志/埋点 | 订单/通知(默认) | 转账/计费 |
选型口诀:
① 能容忍丢 → 最多一次(日志、监控)。
② 不能丢但要简单 → 至少一次 + 业务幂等(90% 系统的答案)。
③ 钱相关、既不能丢也不能重 → 精确一次(幂等生产者 + 事务 + 幂等消费)。
终极心法: 别纠结"精确一次"这个名词。实际工程里,「至少一次 + 幂等消费」 就能解决 99% 的重复问题,且简单可靠。精确一次是为那 1% 强一致场景准备的"重武器"。