消息传递的三种语义: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 消息接收端 说明: • At Most Once:发送端发完即忘,不等待确认,消息可能丢失 • At Least Once:发送端等待确认,超时重试,消息可能重复 • Exactly Once:发送端+接收端+存储三方协作,消息不丢不重 • 虚线表示该语义下可能出现的异常路径(丢失或重复)

如何选择?

嗯,这里有个简单的决策思路:

  1. 能接受丢消息吗?能 → At Most Once
  2. 不能丢,但能接受重复吗?能 → At Least Once + 幂等
  3. 既不能丢也不能重复?→ Exactly Once(准备好接受复杂度)

我的忠告:别一上来就追求 Exactly Once。我见过太多团队,业务场景其实只需要 At Least Once + 幂等,却非要上分布式事务,结果系统又慢又难维护。先想清楚:重复消息的代价是什么?丢失消息的代价又是什么?权衡之后再做选择。

这三种语义是消息系统的"三原色"。理解了它们,后面讲 Exactly Once 的具体实现时,你就能明白为什么需要事务消息、为什么需要幂等表、为什么需要分布式协调。咱们一步一步来。