用户下单后,需要扣库存、发短信、积积分。若下单接口同步等待全部步骤,任一慢服务都会拖住用户。消息队列让下单服务先提交核心结果,再把后续工作作为消息交给消费者异步处理。
解决的问题
异步、削峰和解耦:生产者不必知道消费者何时上线,突发消息可在队列里排队。
新增的问题
消息可能丢、重复、延迟、乱序或积压。系统从一次本地调用变成跨网络的多段责任转移。
MQ(Message Queue,消息队列)是在生产者和消费者之间暂存、转发消息的系统。可靠交付不是打开一个配置开关,而是逐段确认“生产者是否交给 Broker、Broker 是否安全保存、消费者是否完成业务”。Broker 是负责接收、保存和投递消息的服务端。
用户下单后,需要扣库存、发短信、积积分。若下单接口同步等待全部步骤,任一慢服务都会拖住用户。消息队列让下单服务先提交核心结果,再把后续工作作为消息交给消费者异步处理。
异步、削峰和解耦:生产者不必知道消费者何时上线,突发消息可在队列里排队。
消息可能丢、重复、延迟、乱序或积压。系统从一次本地调用变成跨网络的多段责任转移。
Publisher Confirm(发布确认)是 Broker 接管消息后返回给生产者的回执;Consumer Acknowledgement(消费确认,简称 ack)是消费者完成业务后返回给 Broker 的回执。它们方向相反,各自只覆盖一段责任转移。图中的 order.created 是“订单已创建”事件名,messageId=88 是这条消息用于识别重发的唯一编号。
Publisher Confirm(发布确认)是 Broker 告诉生产者“我已经按当前队列规则接管这条消息”的回执。网络断开时,生产者可能无法区分“消息没到”和“消息到了但确认没回来”。
| 动作 | 防住什么 | 仍可能发生 |
|---|---|---|
| 设置消息唯一 ID | 让重复可识别 | 本身不阻止重复 |
| 等待发布确认 | 发现未被 Broker 接管的消息 | 确认丢失导致重发和重复 |
| 不可路由返回/mandatory | 发现交换机没有匹配队列 | 不代表消费者完成业务 |
| 有界重试与退避 | 应对瞬时网络故障 | 长故障需落盘、告警和人工处置 |
持久化表示消息和队列元数据按配置写入可恢复存储;复制表示多个节点保存消息副本。单机写盘防进程重启,多副本应对节点损坏,但都会增加写入延迟、磁盘和网络成本。
队列自身在重启后仍存在。只让队列 durable,不等于每条消息都按持久方式发布。
消息要求落入可恢复存储。真正保证取决于队列类型与确认时机。
例如 RabbitMQ quorum queue 使用多数副本思路提高节点故障下的数据安全。
Consumer Acknowledgement(消费确认,简称 ack)是消费者告诉 Broker“这条投递已经处理成功,可以删除”。自动确认通常在消息刚送出时就转移责任,消费者随后崩溃可能直接丢业务。
| 语义 | 白话含义 | 常见实现 | 业务要求 |
|---|---|---|---|
| At-most-once(至多一次) | 可能 0 次或 1 次,不重试 | 提前确认、失败不重发 | 能接受丢失 |
| At-least-once(至少一次) | 一定努力送到,但可能多次 | 确认、超时重发、未 ack 重投 | 消费者幂等 |
| Exactly-once(恰好一次效果) | 最终业务结果只发生一次 | 特定系统内事务或幂等写入 | 必须说明覆盖边界 |
订单提交与消息发布属于两个系统。先提交订单再发消息,可能在中间崩溃;先发消息再提交订单,消费者可能收到一个后来回滚的订单。
BEGIN;
INSERT INTO orders(id, status) VALUES (42, 'PAID');
INSERT INTO outbox(message_id, topic, payload, status)
VALUES ('msg-88', 'order.paid', '{"orderId":42}', 'NEW');
COMMIT;
订单事件、通知、日志、异步任务与削峰适合消息队列,因为生产者和消费者可以异步推进。同步调用必须立刻得到结果、强事务又无法接受补偿的流程,不应为了“解耦”硬塞消息队列。