952 字
约 3 分钟
8
S17-J03 · 可靠投递:不丢、不重、有序
适用读者:出现过"消息丢了"或"扣了两次钱"的人。
前置:S17-J01。
实测环境:Python 模拟(至少一次投递 + 幂等键),2026-11-05。
覆盖旧文:1001(95) 894(85) 893(81) 1000(59) 1006(72) 172(63) 890(85) = 540 点。
覆盖扩展:E-S17-006(至少一次 → 幂等)。
上一篇:S17-J02 · MQ 选型:RabbitMQ / Kafka / RocketMQ
下一篇:S17-J04 · 分布式事务。
1. 一句话结论
"不丢"和"不重"不可同时免费得到:要保证不丢,就得允许重发;一旦允许重发,消费端就必须幂等。
实测(10 笔支付 × 100 元,至少一次投递共 14 条消息):
投递消息总数: 14
无幂等:应用 14 次 → 余额 1400(应为 1000,多出 400)
有幂等:应用 10 次 → 余额 1000(应为 1000,多出 0)
多出的 400 元 = 4 次重复消费——这就是"至少一次"的真实代价。
2. 三个环节各自可能丢/重
| 环节 | 可能问题 | 对策 |
|---|---|---|
| 生产 | 发送超时但实际成功 | 重试 + 幂等键 |
| Broker | 刷盘前宕机 | 同步刷盘/多副本 |
| 消费 | 处理完成未提交位点 | 幂等 + 位点提交顺序 |
"先处理后提交位点"还是"先提交后处理":
| 顺序 | 结果 |
|---|---|
| 先处理再提交 | 崩溃 → 重复消费(可接受,靠幂等) |
| 先提交再处理 | 崩溃 → 消息丢失(不可接受) |
结论:宁可重复,不可丢失 → 所以幂等是必需品。
3. 幂等的三种实现(E-S17-006)
| 方式 | 说明 | 局限 |
|---|---|---|
| 幂等键 + 去重表 | 唯一索引插入成功才处理 | 需业务键 |
| 状态机 | 只有特定状态可执行 | 需状态明确 |
| 乐观锁/版本号 | 版本不匹配则跳过 | 需读改写 |
判据:"有天然业务键(订单号/支付流水)吗?" 有 → 去重表(S07-J05 的结论在这里同样成立)。
4. 有序的三条边界
| 需求 | 做法 |
|---|---|
| 同订单有序 | 用订单号做分区键(J01) |
| 全局有序 | 单分区(吞吐极低,慎用) |
| 消费端并发有序 | 同 key 串行消费(内存队列分桶) |
"消费端多线程会不会乱序":会——即使分区有序,消费端并发处理也会乱序。要么单线程,要么按 key 分桶串行。
5. 五个误区
| 误区 | 真相 |
|---|---|
| "MQ 保证不丢" | 三个环节都可能丢 |
| "消费成功就提交位点最安全" | 先处理再提交才不丢 |
| "重复消费概率很低可以不管" | 实测 10 笔就多 4 次 |
| "分区有序=消费有序" | 消费端并发会打乱 |
| "加个去重表就万事大吉" | 需业务键且有清理策略 |
6. 自检
- 三个环节各自怎么丢?
- "先处理还是先提交"怎么选、为什么?
- 幂等的三种实现与前提?
- 分区有序为什么保证不了消费有序?
- 实测里 14 条消息造成了多少偏差?
7. 下一篇
S17-J04 · 分布式事务:2PC/TCC/Saga/本地消息表
可靠性讲完,下一篇处理"跨库写"这个更根本的问题。
本篇属于系列《S17 · 分布式与消息队列》。旧文来源:A-1001/894/893/1000/1006/172/890;扩展点 E-S17-006。余额偏差为 Python 实测模拟。
S17-J03 · 可靠投递:不丢、不重、有序
https://www.clxhxhhr.top/posts/5426/ 评论
0 条
还没有评论,先写一条吧。