• 可靠消息服务定时查询状态为已发送并超时的消息
  • 可靠消息将消息重新投递到 MQ 组件中
  • 下游应用监听消息,在满足幂等性的条件下,重新执行业务。
  • 下游应用通知可靠消息服务该消息已经成功消费。
  • 通过消息状态确认和消息重发两个功能,可以确保上游应用、可靠消息服务和下游应用数据的最终一致性。

五条描述的是”本地消息表/可靠消息服务”模式的重发闭环。要把它落地成可靠系统,还需补全:消息丢失的三个环节与各自对策(重发只解决其一)、本地消息表与事务消息两种实现形态、以及重发策略的参数设计。本文补全成完整的可靠性方案。

消息丢失的三环节与对策全景

环节 丢失原因 对策
生产→Broker 发送超时/Broker 宕机 同步发送+重试 / 事务消息 / 本地消息表补偿(本文重点)
Broker 存储 未刷盘宕机 / 副本未同步 同步刷盘/同步复制配置(权衡吞吐)
Broker→消费 自动 ack 后消费失败 / 重平衡丢消息 手动 ack(处理成功才确认)+ 消费失败重试

重发补偿解决的是第一环节(生产侧不确定)与消费失败的再投递;三环节要各自设防才叫端到端可靠。

实现形态一:本地消息表(原文方案的完整化)

  1. 上游业务事务内:写业务表 + 写消息表(status=待发送)——同事务保证”业务成功必有消息记录”
  2. 独立发送线程/定时任务:扫消息表投递 MQ,成功置 status=已发送
  3. 定时补偿:扫”已发送但超时未确认”的记录重投(原文第 1、2 条)
  4. 下游消费成功:回调/消息确认置 status=已完成(原文第 4 条)
  5. 下游消费幂等:重投的消息靠幂等键去重(原文第 3 条)

要点:消息表与业务表同库同事务(跨库则失去原子性);补偿扫描的”超时”要大于正常确认时长防误重投;消息表定期归档清理。

实现形态二:事务消息(RocketMQ 原生)

  1. 发 half 消息(对消费者不可见)
  2. 执行本地事务
  3. 提交/回滚 half 消息(commit 则投递、rollback 则丢弃)
  4. 回查:Broker 长时间未收到二阶段结果时反查本地事务状态——替代本地消息表的定时扫描

优势:无消息表、无扫描任务;代价:绑定支持事务消息的 MQ(RocketMQ),回查逻辑仍需实现(查业务状态)。

重发策略参数设计

  • 重试次数与退避:3-5 次、指数退避(1s/5s/30s/2m/10m),避免故障期反复冲击
  • 重试载体:MQ 原生重试队列(RocketMQ %RETRY% topic / RabbitMQ dead-letter+delay)或补偿任务重投
  • 终点是死信:重试耗尽进死信队列 + 告警 + 人工/脚本补偿——重发不是无限重试,而是”重试+兜底”的闭环
  • 幂等键贯穿:消息 ID/业务键在重投中不变,下游去重表据此判重(重发安全的前提)

最终一致性的确认语义

原文第 5 条的”最终一致性”要精确化:

  • 上游业务成功 ⇒ 消息最终必被成功消费一次(幂等意义下)——靠”同事务落表 + 补偿重投 + 幂等消费”三角保证
  • 不保证时限(最终=有界重试+死信人工兜底内的”最终”)
  • 对账任务兜底:业务表与下游状态周期比对,修复极端遗漏(补偿的补偿)

与 at-least-once 语义的关系

MQ 的 at-least-once(至少一次)+ 消费幂等 = effectively-once(效果上恰好一次)。重发补偿是生产侧的 at-least-once 保证,幂等是消费侧的去重保证,两者相乘才是端到端恰好一次——只谈重发不谈幂等,重发就是重复事故制造机。

重发补偿的思路一句话:**把”不确定的发送”变成”确定的记录 + 不确定的投递 + 确定的补偿”**;本地消息表与事务消息是它的两种载体,退避重试与死信兜底是它的策略两翼,幂等消费是它安全的前提。五者齐备,最终一致性才从口号变成可验收的系统性质。