消息队列MQ
一、MQ 是什么
消息队列(Message Queue)就是一个异步通信的中间人——生产者把消息扔到队列里就走,消费者自己来取。两边不需要同时在线,不需要直接通信。
graph LR subgraph Without[没有MQ 强耦合] A1[A] -->|直接调| B1[B] B1 -.->|B挂了| C1[A也挂了] end subgraph With[有MQ 解耦] A2[A] -->|扔消息| MQ[MQ] MQ -->|慢慢消费| B2[B] B2 -.->|B挂了消息还在MQ| C2[重启继续消费] end
二、MQ 三大核心场景
2.1 解耦
系统之间不直接调用,通过 MQ 中转。任何一方挂了不影响另一方。
graph LR subgraph Old[旧架构 强耦合] O_Order[订单系统] --> O_Stock[库存系统] O_Order --> O_Logistics[物流系统] O_Order --> O_Notice[通知系统] O_Order --> O_Points[积分系统] O_Fail[任一挂了→全连带挂] end subgraph New[新架构 MQ解耦] N_Order[订单系统] --> N_MQ[MQ] N_MQ --> N_Stock[库存系统] N_MQ --> N_Logistics[物流系统] N_MQ --> N_Notice[通知系统] N_MQ --> N_Points[积分系统] N_Fail[任一挂了→MQ消息还在<br/>重启后继续消费] end
一句话:A 系统不直接调 B,只往 MQ 里扔消息。B 什么时候处理、处理多慢,A 完全不关心。
2.2 异步
核心链路走完就返回,非核心链路扔 MQ 异步处理。
graph LR subgraph Sync[同步 慢] S1[注册 50ms] --> S2[发邮件 300ms] S2 --> S3[发短信 200ms] S3 --> S_R[返回 550ms] end subgraph Async[异步 快] A1[注册 50ms] --> A2[扔消息 5ms] A2 --> A_R[返回 55ms] A2 --> MQ[MQ] MQ --> A3[发邮件 后台] MQ --> A4[发短信 后台] end
一句话讲清:“用户只关心注册是否成功,不关心邮件什么时候发。核心链路走完就返回,非核心链路扔 MQ 异步处理。“
2.3 削峰填谷
用 MQ 做缓冲池——高峰流量先堆在 MQ 里,消费者按自己的节奏慢慢处理。
graph TD Peak[瞬时流量: 10,000 QPS<br/>服务器上限仅1,000 QPS] --> MQ[缓冲层: MQ<br/>来多少收多少] MQ --> Stable[平稳消费: 1,000 QPS<br/>消费者按自己节奏处理]
必背:削峰填谷 = MQ 做缓冲池。高峰堆 MQ,低谷慢慢消。不让流量峰值直接把后端打挂。
三、两种消息模型
3.1 点对点(Queue — 抢单模式)
一条消息只能被一个消费者消费。谁抢到算谁的。
graph LR P[Producer] --> Q[Queue<br/>msg3 msg2 msg1] Q --> CA[Consumer A 抢到msg1] Q --> CB[Consumer B 抢到msg2] Q --> CC[Consumer C 抢到msg3]
适用场景:下单后减库存——只能一个服务处理,不能反复减。
3.2 发布/订阅(Topic — 广播模式)
一条消息所有订阅者都能收到。
graph LR P[Producer] --> T[Topic] T --> SA[Subscriber A 收到] T --> SB[Subscriber B 收到] T --> SC[Subscriber C 收到]
适用场景:价格变了 → 通知 App、通知 Web、发推送给用户——所有渠道都收到。
3.3 对比
| 模型 | 消息怎么分 | 类比 | 场景 |
|---|---|---|---|
| 点对点 | 一条消息一人拿 | 抢红包 | 订单处理、任务分发 |
| 发布订阅 | 一条消息人人拿 | 订阅公众号 | 配置变更通知、实时行情推送 |
四、RabbitMQ vs Kafka vs RocketMQ
4.1 一句话选型
-
RabbitMQ:万级吞吐,功能全面,延迟低 → 适合业务系统(注册发邮件、订单通知)
-
Kafka:百万级吞吐,顺序写磁盘 → 适合日志收集、流处理、大数据管道
-
RocketMQ:十万级吞吐,事务消息强 → 适合电商/金融(下单、支付、发货)
4.2 详细对比
| 维度 | RabbitMQ | Kafka | RocketMQ |
|---|---|---|---|
| 吞吐量 | 万级 | 百万级 | 十万级 |
| 延迟 | 微秒级 | 毫秒级 | 毫秒级 |
| 消息可靠性 | 高 | 高 | 高 |
| 事务消息 | 不好用 | 不支持 | 原生支持 |
| 延迟消息 | 插件支持 | 不支持 | 原生支持(18 个级别) |
| 顺序消息 | 不好保证 | 分区内有序 | 全局有序 |
| 社区生态 | 最成熟 | 大数据标配 | 阿里系为主 |
| 学习成本 | 低 | 中 | 中 |
一句话讲清:“选什么取决于场景——做业务系统用 RabbitMQ,做日志/流处理用 Kafka,做电商/金融用 RocketMQ。不要说’只会一种所以选它’。“
4.3 RabbitMQ 核心概念
| 概念 | 说明 |
|---|---|
| Broker | RabbitMQ 服务器本体 |
| Exchange | 交换机——收 Producer 的消息,按规则路由到 Queue |
| Queue | 队列——存消息,Consumer 从这里取 |
| Binding | 绑定——Exchange 和 Queue 的路由规则 |
| Routing Key | 路由键——Exchange 根据这个决定消息去哪个 Queue |
三种 Exchange 类型:
-
Direct:精确匹配 Routing Key →
routing_key="order"只进 order 队列 -
Fanout:广播,无视 Key → 所有绑定的 Queue 都收到
-
Topic:模糊匹配 Key →
routing_key="order.*"匹配 order.create、order.cancel
五、四大经典问题
5.1 消息丢失
消息丢失发生在三个环节,每个环节都要防:
| 环节 | 防丢方案 | 说明 |
|---|---|---|
| 发送环节 | Publisher Confirm / acks=all | 生产者确认消息到达 Broker |
| 存储环节 | 持久化 + 多副本 | 消息写磁盘 + 多节点复制 |
| 消费环节 | 手动 ack | 处理完再确认,处理到一半挂了会重发 |
graph LR Send[发送确认<br/>Publisher Confirm] --> Persist[持久化+多副本<br/>消息写磁盘+多节点复制] Persist --> Ack[手动ack<br/>处理完再确认] Send -.-> Fail[任何一道防线缺失<br/>→ 消息就可能丢]
5.2 消息重复消费
MQ 大多是”至少一次”投递——ack 超时会重发,消息可能被处理两次。
解决方案:幂等性(同一个操作执行一次和一百次,结果一样)
| 方案 | 原理 | 可靠度 |
|---|---|---|
| 数据库唯一键 | INSERT msg_id 加唯一索引,重复插入报错 | 最强 |
| Redis 标记 | SET msg_id "done" NX,消费前先查 | 高 |
| 业务天然幂等 | UPDATE SET money=100 WHERE id=1,执行多次结果一样 | 看业务 |
必背口诀:接口幂等 = 同一个操作执行一次和一百次,结果一样。MQ 重复消费的本质是”至少一次”投递——要自己保证幂等。
5.3 消息乱序
Producer 按 ①→②→③ 发送,但 ② 失败重试 → Consumer 收到 ①→③→②(乱序)。
| MQ | 解决方案 | 代价 |
|---|---|---|
| RabbitMQ | 需要顺序的消息路由到同一队列 | 单消费者处理,并行度低 |
| Kafka | 同一 key 发到同一 partition | 吞吐受限于单 partition |
| RocketMQ | 原生支持全局顺序消息 | 吞吐量大幅下降 |
一句话讲清:“能不用顺序消息就不用——顺序消息牺牲吞吐量。大部分业务靠最终一致性解决,不需要严格顺序。“
5.4 消息积压
Producer 发太快 + Consumer 处理慢 → MQ 越堆越多。
排查步骤:
-
先看 Consumer 哪里慢了(下游数据库慢?接口超时?)
-
临时加 Consumer 实例(水平扩展,最快方案)
-
如果积压量太大(几千万条),写临时程序批量拉消息 → 不做处理 → 先写到另一个队列 → 等积压消掉后补数据
预防:设置队列容量上限(满了拒绝新消息)+ 监控积压告警。
六、踩坑记录
| 坑 | 正确认知 |
|---|---|
| 以为 MQ 能保证消息不丢 | MQ 不做额外配置的话丢了是正常的!发送确认 + 持久化 + 手动 ack 三件套缺一不可 |
| 以为 MQ 能保证不重复 | 绝大多数 MQ 是”至少一次”投递——重复是必然的,必须自己实现幂等 |
| 以为 MQ 天然保序 | 多分区/多队列 + 重试 = 乱序。需要顺序就别扩分区 |
| 自动 ack 直接干活 | 自动 ack = 收到就确认。消费者处理到一半挂了,消息已”确认”,永远丢了 |
| 所有场景都用 MQ | MQ 不是银弹。加了 MQ = 多一个中间件要维护、多了延迟、多了一致性问题 |
| MQ 异步后数据不一致 | A 写 DB 成功,发 MQ 失败 → B 收不到 → 数据不一致。需要本地消息表或事务消息兜底 |
七、快速问答
| 问题 | 一句话答案 |
|---|---|
| 消息队列是干什么的? | 解耦异步削峰——服务之间不直接调,通过队列中转 |
| MQ 三大核心场景? | 解耦(不直接调)、异步(不等结果)、削峰(缓冲池) |
| 点对点和发布订阅区别? | 点对点一条消息一人拿(抢红包),发布订阅一条消息人人拿(公众号) |
| RabbitMQ 和 Kafka 区别? | RabbitMQ 万级低延迟适合业务,Kafka 百万级高吞吐适合日志流处理 |
| 消息丢了怎么办? | 发送确认 + 持久化 + 手动 ack——三道防线覆盖生产→存储→消费 |
| 消息重复消费怎么处理? | 接口幂等——数据库唯一键去重、Redis 标记、业务天然幂等 |
| 怎么保证消息顺序? | 需要顺序的消息进同一队列/分区,但会牺牲吞吐量 |
| 消息积压了怎么处理? | 先看 Consumer 瓶颈 → 加实例水平扩展 → 不行就临时扩容队列 |
| 什么时候不该用 MQ? | 系统还不够复杂时——多一个中间件多一个故障点 |
| 什么是消息幂等性? | 同一个消息消费一次和消费十次结果一样——靠唯一键/状态标记保证 |
| 为什么 Kafka 吞吐量高? | 顺序写磁盘 + 零拷贝 + 批量发送 + 分区并行——每个设计都在追求吞吐 |
| 事务消息是什么? | RocketMQ 特性——本地事务和消息发送在同一事务中,保证要么都成功要么都失败 |
速记卡(面试闪卡)
Q1:一句话讲清「消息队列MQ」到底是什么?
A:消息队列是系统间的”中转邮局”:生产者扔进队列就走,消费者自己慢慢取,两边不需同时在线。
Q2:一、MQ 是什么 —— 怎么理解? —— 怎么理解?
A:像小区快递柜:你(生产者)把包裹塞进柜子就走,收件人(消费者)啥时候来取都行,彼此不用碰面。没有 MQ 时 A 直接调 B,B 一挂 A 也跟着死;有 MQ 后消息堆在柜里,B 重启接着取。英文:Message Queue / broker。
Q3:二、三大核心场景 —— 怎么理解? —— 怎么理解?
A:像三件家务:解耦是”我扔垃圾你自倒,互不耽误”;异步是”核心链路走完就回,发邮件这种杂活后台慢慢干”;削峰填谷是”双十一订单先堆在池子里,仓库按节奏慢慢发,别被峰值冲垮”。英文:decoupling / async / peak shaving。
Q4:三、两种消息模型 —— 怎么理解? —— 怎么理解?
A:像两种群发:点对点(Queue)是抢红包,一条消息只能一人抢到,适合”减库存”只一人处理;发布订阅(Topic)是公众号推送,一条消息人人收到,适合”价格变了通知所有渠道”。英文:Point-to-Point / Pub-Sub。
Q5:四、选型与四大问题 —— 怎么理解? —— 怎么理解?
A:像选车:RabbitMQ 万级吞吐、低延迟,适合业务系统;Kafka 百万级、顺序写盘,适合日志流处理;RocketMQ 十万级、事务消息强,适合电商金融。四大坑:丢消息(确认+持久化+手动ack)、重复(幂等)、乱序(同分区)、积压(加实例)。英文:idempotent / at-least-once。
Q6:核心速记主线有哪些?
-
本质:异步通信中间人,生产消费解耦、不必同时在线
-
三场景:解耦(不直接调)、异步(不等结果)、削峰(缓冲池)
-
两模型:点对点一条一人拿(抢红包)、发布订阅一条人人拿(公众号)
-
选型:RabbitMQ 业务、Kafka 日志流、RocketMQ 电商金融
-
四难题:防丢三件套、幂等防重复、同分区保序、加实例消积压
口诀
A:消息队列中转站,生产扔了消费捡;
解耦异步削峰三,缓冲池里稳如堰;
点对抢红订阅号,一条众人皆可见;
选型看场景,丢重乱积四道关。