文章目录
- 1.三种投递模型
- 2.至少一次投递详解
-
- [2.1 工作原理](#2.1 工作原理)
- [2.2 优缺点](#2.2 优缺点)
- [2.3 典型应用场景](#2.3 典型应用场景)
- 3.三种模型的实现原理
-
- [3.1 至多一次 (At Most Once)](#3.1 至多一次 (At Most Once))
- [3.2 至少一次 (At Least Once)](#3.2 至少一次 (At Least Once))
- [3.3 恰好一次 (Exactly Once)](#3.3 恰好一次 (Exactly Once))
- 4.主流消息队列的投递保证
- 5.实际开发中的选择策略
- 6.总结
- 参考文献
在分布式消息系统中, 投递模型 定义了消息从生产者发送到消费者接收的可靠性保证。其中 "至少一次投递" 是最常用、也是理解其他模型的基础。
1.三种投递模型
| 模型 | 英文 | 含义 | 可能情况 |
|---|---|---|---|
| 至多一次 | At Most Once | 消息可能丢失,但绝不会重复 | 丢消息 ✅ / 不丢 ❌ / 重复 ❌ |
| 至少一次 | At Least Once | 消息不会丢失,但可能重复 | 丢消息 ❌ / 不丢 ✅ / 重复 ✅ |
| 恰好一次 | Exactly Once | 消息既不丢失也不重复 | 丢消息 ❌ / 不丢 ✅ / 重复 ❌ |
在分布式系统中,Exactly Once 通常是由"至少一次 + 幂等消费"组合实现的理想状态,而不是单一机制能保证的。
2.至少一次投递详解
2.1 工作原理
消费者 消息队列 生产者 消费者 消息队列 生产者 如果消费者 ACK 丢失(网络超时等) 发送消息 M 返回 ACK (确认收到) 拉取消息 返回消息 M 处理完成,返回 ACK 标记消息已消费 重新投递消息 M (可能重复)
核心机制:
- 生产者端:消息队列确认收到后才算成功,否则重发
- 消费者端:必须主动发送 ACK 确认,队列才认为消费完成
- 重试机制:如果超时或收到错误,队列会重新投递
2.2 优缺点
| 优点 | 缺点 |
|---|---|
| ✅ 消息不会丢失(可靠性高) | ❌ 可能收到重复消息 |
| ✅ 实现相对简单 | ❌ 消费者必须实现幂等性 |
| ✅ 大多数消息队列默认支持 | ❌ 重复消息可能影响业务逻辑 |
2.3 典型应用场景
| 场景 | 原因 |
|---|---|
| 订单支付通知 | 支付成功消息绝对不能丢,重复可通过业务去重 |
| 日志采集 | 宁可重复采集,也不能丢失重要日志 |
| 数据同步 | 数据可以重复同步(通过版本号去重) |
| 异步任务 | 任务可以重复执行(需幂等设计) |
3.三种模型的实现原理
3.1 至多一次 (At Most Once)
发送消息
直接投递
无需 ACK
消息可能丢失
生产者
消息队列
消费者
丢弃
实现方式:
- 生产者不等待确认(fire-and-forget)
- 消费者不发送 ACK
- 消息队列收到即丢弃,不持久化
适用场景:监控指标上报、实时视频流、可容忍丢失的日志
3.2 至少一次 (At Least Once)
实现方式:
- 生产者:同步发送 + 重试机制,等待队列 ACK
- 消息队列:消息持久化到磁盘,等待消费者 ACK 后才删除
- 消费者:处理完成后发送 ACK,如果超时或失败,队列重新投递
关键配置(以 Kafka 为例):
properties
# 生产者:等待所有副本确认
acks=all
retries=3
# 消费者:手动提交 offset
enable.auto.commit=false
3.3 恰好一次 (Exactly Once)
实现方式 :通常是 "至少一次" + "幂等消费" 的组合。
确保
确保
至少一次投递
消息不丢
幂等消费
消息不重
恰好一次
技术方案:
| 方案 | 说明 | 代表系统 |
|---|---|---|
| 分布式事务 | 2PC / 3PC 协议 | 传统数据库 |
| 事务消息 | 消息队列内置事务 | RocketMQ、Kafka 事务 |
| 幂等设计 | 业务层去重(更常用) | 数据库唯一键、Redis 去重 |
| 去重表 | 消费者记录处理过的消息 ID | 自定义实现 |
4.主流消息队列的投递保证
| 消息队列 | 默认模型 | 支持的模型 | 说明 |
|---|---|---|---|
| Kafka | 至少一次 | 至多一次、恰好一次(事务) | 通过 acks 和 enable.auto.commit 配置 |
| RocketMQ | 至少一次 | 至多一次、恰好一次 | 同步刷盘 + 同步复制保证不丢 |
| RabbitMQ | 至少一次 | 至多一次 | 通过 publisher confirms + consumer ack |
| Pulsar | 至少一次 | 至多一次、恰好一次 | 支持 Exactly Once 语义 |
| AWS SQS | 至少一次 | --- | FIFO 队列支持 Exactly Once |
5.实际开发中的选择策略
可以
不可以
可以
不可以
业务需求
消息可以丢失吗?
至多一次
可以容忍重复吗?
至少一次 + 业务去重
恰好一次
幂等 + 事务/去重表
日志、监控
订单、支付
金融、对账
| 业务类型 | 推荐模型 | 理由 |
|---|---|---|
| 金融交易 | 恰好一次 | 数据绝对准确,不能丢不能重 |
| 订单支付 | 至少一次 + 幂等 | 不能丢,重复可通过订单号去重 |
| 日志采集 | 至多一次 | 丢几条日志可接受,性能优先 |
| 用户行为分析 | 至少一次 | 宁可重复统计,不能丢数据 |
6.总结
| 模型 | 核心保证 | 实现代价 | 典型场景 |
|---|---|---|---|
| 至多一次 | 不重复,可能丢 | 最低 | 监控、实时视频 |
| 至少一次 | 不丢失,可能重复 | 中等 | 大多数业务场景 |
| 恰好一次 | 不丢不重 | 最高 | 金融、对账 |
一句话总结 :"至少一次"意味着"消息绝对不会丢,但你可能会收到多次"------所以消费者的幂等处理是标配。如果你能做到"至少一次 + 幂等",就等价于实现了"恰好一次"。