黑马商城 · DAY 05 学习页

消息发出去了,
怎么保证它真的办成事?

把 RabbitMQ 想成快递站:寄出、到站、签收每一步都可能出问题。今天用“支付成功通知”和“订单超时取消”,学会可靠投递、失败补救、延迟消息与集群。

01 / 先看整条链路

一条支付消息,要过四道关

用户余额扣减成功后,支付服务把“支付成功”告诉交易服务。

pay-service
生产消息
→
Exchange
交换机
→
Queue
队列
→
trade-service
消费消息
今天的核心:生产者知道消息是否到了交换机、是否进了队列;Broker 重启后消息还能在;消费者失败能重试并留痕;需要晚点处理的消息能按时送达。
可靠投递

发件人有回执

Confirm、Return 和本地消息表,解决“发出后不知道成没成”。

可靠消费

收件人能补救

ACK、重试和错误队列,解决“拿到了但处理失败”。

延迟与集群

稍后处理、降低单点风险

延迟消息做超时关单;仲裁队列和多节点连接提高可用性。

02 / 生产消息可靠性

“发送成功”到底成功到哪一步?

把消息当快递,就容易分清 Confirm 和 Return。

机制它回答的问题失败时怎么办
ConfirmBroker 的交换机收到消息了吗?收到 nack 或发送异常,记录失败并准备重发。
Return交换机能把消息路由进队列吗?开启 mandatory;不可路由时收到退回通知,检查 routing key / 绑定。
本地消息表进程崩溃、连接断开时,原消息还在吗?先把消息存数据库,再发送;失败或待发送的消息定时重试。
最容易误判:Confirm ack 只说明交换机收到了,不证明消息进队列,更不证明消费者业务处理成功。

点一下,判断消息走到了哪一步

项目里的做法:支付服务用 pay_outbox 保存待发消息;确认送达后标为 SENT,失败时后台重试。mq-demo 有通用示例。
03 / 消息持久化

Broker 重启,消息还在吗?

“店铺、货架、包裹”都要能保留下来。

交换机

durable exchange

交换机本身持久化,重启后路由入口才保留。

队列

durable queue

队列本身持久化,重启后收件位置才保留。

消息

persistent message

消息也要持久化,Broker 才会尽力在重启后保留消息。

三个条件不同:持久化交换机 ≠ 持久化队列 ≠ 持久化消息。它们提高重启后的保留能力,发送确认和消费确认仍然需要做。
04 / 消费消息可靠性

消费者收到后报错,谁来收拾?

先确认有没有处理成功,再决定重试还是转存。

队列送来消息
→
执行业务逻辑
→
成功 → ACK
如果失败:自动确认模式下,监听器抛出异常,由 Spring AMQP 按配置做本地重试;耗尽后用 RepublishMessageRecoverer 投到错误队列。手动确认模式则显式调用 basicAck 或 basicNack,决定是否重新入队。

重试要有上限

网络抖动可能重试成功;代码错误通常不会。无限重试会卡住队列,所以耗尽后把原消息和异常留在错误队列,供排查补偿。

重复消费要能承受

业务成功,但 ACK 丢失,Broker 可能再次投递。因此业务还需幂等:例如订单已支付,就不要重复处理。

项目里的做法:消费者本地重试 3 次;仍失败则进入 hmall.error.queue。mq-demo 还演示 ACK / NACK。
05 / 延迟消息

为什么下单后等 35 分钟才查?

延迟消息就是“现在寄出,指定时间后才交给消费者”。

0 分钟创建订单

订单待支付,同时发出 35 分钟后到达的消息。

30 分钟停止接受支付

支付服务先检查期限;过期就拒绝扣余额。

35 分钟检查并取消

交易服务收到消息;若仍未支付,取消订单并回补库存。

为什么是 30 + 35?如果第 30 分钟一边支付一边取消,流程容易打架。支付截止在 30 分钟,取消检查在 35 分钟,中间留 5 分钟缓冲;消费时仍要重新查订单状态。
方案消息怎么“等”关键点
TTL + 死信交换机先进入带过期时间的队列,过期后转发到目标交换机。原生 TTL 和 DLX 组合。
延迟消息插件投给 x-delayed-message 交换机,发送时设置 x-delay。Broker 必须安装并启用插件。
06 / MQ 集群

一台 MQ 坏了,怎么继续用?

讲义介绍普通集群、镜像集群和仲裁队列。先看“消息数据有没有副本”。

类型通俗理解特点
普通集群多个站点知道队列在哪里,消息主要留在所属节点。队列所属节点故障仍可能影响它。
镜像集群把队列内容复制到多个节点。讲义用于理解历史方案;同步和维护有成本。
仲裁队列多个节点保存副本,由多数节点达成一致。通常需要多个节点验证故障切换。
当前环境的真实状态:课程虚拟机目前只看到一个 RabbitMQ 节点。项目里有仲裁队列和多地址连接代码示例;单节点不能证明“三节点故障切换成功”。
07 / 每日作业

订单超时取消,把知识连起来

讲义作业:30 分钟内不支付则不可再支付,35 分钟后自动取消未支付订单。

  1. 创建订单:写订单、扣库存,发送 35 分钟延迟消息,消息里放订单 ID。
  2. 用户支付:先查订单是否有效、是否超过 30 分钟;有效才扣余额。
  3. 35 分钟后:消费者查最新状态;已支付直接结束,未支付且超时才取消。
  4. 取消后的补偿:把已扣库存加回去;重复收到消息也不能重复加库存。
项目已验证

未支付订单

短延迟和回拨时间模拟到期:订单取消,库存回补;取消后支付被拒绝,余额未扣。

项目已验证

已支付订单

延迟消息到达后仍保持已支付状态;支付成功消息由 outbox 保存并发送。

测试范围:用了短延迟和回拨数据库时间,没有等待真实 30 / 35 分钟;也没有在多节点环境验证故障切换。
08 / 自测

不看上文,答完再批改

错题会给出解释。

09 / 20 分钟复习

能讲给别人听,才算真的会

掌握度

推荐顺序

前 5 分钟:画四站消息链路,写出每站的故障。

接着 7 分钟:自己讲 Confirm、Return、持久化、ACK、重试和错误队列。

再用 5 分钟:画订单 0 / 30 / 35 分钟时间线。

最后 3 分钟:做小测,勾选清单。