消息队列 RabbitMQ 入门
消息队列是微服务架构中解耦、异步、削峰的核心中间件。本篇聚焦 RabbitMQ 的架构与最佳实践。
为什么需要消息队列
解耦、异步、削峰是三大核心场景。
解耦
订单系统无需直接调用库存、积分、短信等服务,只需将订单事件写入 MQ,下游服务异步消费。
订单系统 -> MQ -> 库存系统
-> 积分系统
-> 短信服务异步 (性能提升)
同步: 写库(50ms) + 发邮件(300ms) + 发短信(300ms) = 650ms
异步: 写库 + 发消息到 MQ = 55ms (下游异步消费)削峰
高并发时将请求先写入 MQ,消费者按自身能力拉取处理,保护数据库不被冲垮。
主流 MQ 对比
| 中间件 | 特点 | 适用场景 |
|---|---|---|
| RabbitMQ | 稳定可靠,路由丰富,Erlang 实现 | 业务消息、异步、延迟 |
| RocketMQ | 高吞吐、高可用,Java | 电商、交易、金融 |
| Kafka | 超高吞吐,适合流式数据 | 日志采集、埋点、实时计算 |
RabbitMQ 核心模型
Producer -> Exchange -> Queue -> Consumer| 角色 | 说明 |
|---|---|
| Producer | 发送消息 |
| Consumer | 消费消息 |
| Broker | RabbitMQ 服务节点 |
| Exchange | 交换机,根据路由规则投递 |
| Queue | 队列,存储消息 |
| Binding | Exchange 与 Queue 的绑定关系 |
| Routing Key | 路由键,决定消息去向 |
| Channel | 复用 TCP 的逻辑通道 |
Exchange 类型
direct — 精确匹配
Routing Key 完全一致才投递。适合按业务类型分发。
绑定: order.created -> order.created.queuefanout — 广播
无视 Routing Key,将消息发给所有绑定的队列。适合全局通知。
商品修改事件 -> 商品搜索服务、索引服务、缓存服务topic — 通配匹配
* 匹配一个单词
# 匹配零个或多个单词
order.* -> order.created, order.cancelled
product.# -> product.price.changed, product.stock.changed消息确认机制
生产者确认 (Publisher Confirm)
确保消息成功到达 Broker,失败可重试或记录日志。
消费者确认 (Consumer Ack)
| 方式 | 说明 |
|---|---|
| 自动确认 | 投递即删除,可能丢消息 |
| 手动确认 | 处理完业务逻辑后调用 ack |
推荐手动确认:
消费消息 -> 执行业务 -> 成功 ack / 失败 nack 或重回队列可靠投递三阶段
| 阶段 | 风险 | 对策 |
|---|---|---|
| 发送阶段 | 生产者发送失败 | Publisher Confirm、失败重试 |
| 存储阶段 | MQ 宕机丢数据 | 队列 + 消息持久化 |
| 消费阶段 | 消费者处理失败 | 手动 ack、重试、死信队列 |
重复消费与幂等性
消息系统无法保证仅消费一次,消费者必须做幂等:
- 业务唯一 ID 去重
- 数据库唯一索引
- Redis 布隆过滤器
- 状态机校验
收到 payment.success 消息
-> 查询订单状态
-> 已支付:直接返回
-> 未支付:更新状态 + 流水记录死信队列 (DLQ)
处理无法被正常消费的消息,用于异常隔离和人工排查:
消费失败 -> 重试耗尽 -> 死信队列 -> 人工介入TTL 与延迟消息
通过死信交换器 + TTL 实现延迟消息:
发消息到延迟队列 (TTL=30min)
-> TTL 到期
-> 死信交换器转发到业务队列
-> 消费者处理适用于:订单超时关闭、优惠券到期提醒。
消息分发控制
prefetch = 1 # 每次只给消费者一个未确认消息保证消费者之间负载均衡,避免消息堆积在某个慢消费者。
常见注意事项
- 持久化队列 + 持久化消息缺一不可
- 异步系统中需考虑最终一致性
- 幂等设计是必须的,不要依赖消息不重复
- 死信队列 + 告警机制必须配套
- MQ 不适用于强一致性场景