消息传递的三种语义:At Most Once、At Least Once、Exactly Once
做消息中间件这些年,我见过太多因为语义选型翻车的案例。说白了,消息传递语义就是告诉你:这条消息到底能保证被处理几次。这是分布式系统的基石,也是 Exactly Once 的起点。
今天咱们就把这三种语义掰开揉碎,看看它们各自的定义、实现方式,以及适合什么场景。
1. At Most Once(最多一次)
定义:消息最多被投递一次。投递后无论成功还是失败,都不会重试。
你想想看,这种语义下,消息可能丢失,但绝不会重复。实现起来最简单:发出去就不管了。
核心特征:
- 发送后不等待确认
- 不维护重试机制
- 允许消息丢失,不允许重复
适用场景:
- 日志采集(丢几条日志无所谓)
- 监控指标上报(重复数据反而会干扰统计)
- 非关键通知(比如用户点赞的动画效果)
我的经验:做 IoT 设备数据采集时,我习惯用 At Most Once。设备上报温度数据,每秒一次,丢一两条不影响整体趋势。如果硬要保证不丢,反而会把设备搞死——重试队列撑爆内存,这我踩过坑。
2. At Least Once(至少一次)
定义:消息至少被投递一次。发送方会持续重试,直到收到接收方的确认。
这是生产环境最常用的语义。消息不会丢,但可能重复。为什么?因为确认可能丢失,发送方会重试,导致接收方收到多条相同消息。
核心特征:
- 发送方等待 ACK
- 超时或失败时自动重试
- 允许消息重复,不允许丢失
适用场景:
- 支付通知(宁可重复通知,不能漏通知)
- 订单状态变更(丢失订单更新会出大问题)
- 短信/邮件发送(重复发送比不发送好)
避坑指南:我曾经在金融项目中只用 At Least Once,结果下游消费端没做幂等,同一个订单被重复处理了两次,账对不上了。记住:用 At Least Once 必须配合幂等设计,否则重复消息就是灾难。
3. Exactly Once(恰好一次)
定义:消息恰好被处理一次,既不丢失,也不重复。
这是分布式系统里的"圣杯"。实现起来最复杂,需要发送端、接收端、存储系统三方协作。
核心特征:
- 消息不丢失
- 消息不重复
- 需要分布式事务或幂等+去重机制
适用场景:
- 金融交易(转账、扣款)
- 库存扣减(超卖不可接受)
- 计费系统(重复计费会引发投诉)
我的看法:Exactly Once 不是银弹。它的代价是性能下降和复杂度飙升。我见过团队为了追求 Exactly Once,把系统延迟从 10ms 搞到 500ms,得不偿失。选型前先问自己:业务真的需要 Exactly Once 吗?
三种语义对比
| 语义 | 消息丢失 | 消息重复 | 实现复杂度 | 性能影响 |
|---|---|---|---|---|
| At Most Once | 允许 | 不允许 | 低 | 无 |
| At Least Once | 不允许 | 允许 | 中 | 较小 |
| Exactly Once | 不允许 | 不允许 | 高 | 较大 |
核心逻辑关系图
下面这张图展示了三种语义在消息传递链路中的位置和关系:
如何选择?
嗯,这里有个简单的决策思路:
- 能接受丢消息吗?能 → At Most Once
- 不能丢,但能接受重复吗?能 → At Least Once + 幂等
- 既不能丢也不能重复?→ Exactly Once(准备好接受复杂度)
我的忠告:别一上来就追求 Exactly Once。我见过太多团队,业务场景其实只需要 At Least Once + 幂等,却非要上分布式事务,结果系统又慢又难维护。先想清楚:重复消息的代价是什么?丢失消息的代价又是什么?权衡之后再做选择。
这三种语义是消息系统的"三原色"。理解了它们,后面讲 Exactly Once 的具体实现时,你就能明白为什么需要事务消息、为什么需要幂等表、为什么需要分布式协调。咱们一步一步来。