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. 自检

  1. 三个环节各自怎么丢?
  2. "先处理还是先提交"怎么选、为什么?
  3. 幂等的三种实现与前提?
  4. 分区有序为什么保证不了消费有序?
  5. 实测里 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/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。