消息队列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 详细对比

维度RabbitMQKafkaRocketMQ
吞吐量万级百万级十万级
延迟微秒级毫秒级毫秒级
消息可靠性
事务消息不好用不支持原生支持
延迟消息插件支持不支持原生支持(18 个级别)
顺序消息不好保证分区内有序全局有序
社区生态最成熟大数据标配阿里系为主
学习成本

一句话讲清:“选什么取决于场景——做业务系统用 RabbitMQ,做日志/流处理用 Kafka,做电商/金融用 RocketMQ。不要说’只会一种所以选它’。“

4.3 RabbitMQ 核心概念

概念说明
BrokerRabbitMQ 服务器本体
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 越堆越多。

排查步骤

  1. 先看 Consumer 哪里慢了(下游数据库慢?接口超时?)

  2. 临时加 Consumer 实例(水平扩展,最快方案)

  3. 如果积压量太大(几千万条),写临时程序批量拉消息 → 不做处理 → 先写到另一个队列 → 等积压消掉后补数据

预防:设置队列容量上限(满了拒绝新消息)+ 监控积压告警。


六、踩坑记录

正确认知
以为 MQ 能保证消息不丢MQ 不做额外配置的话丢了是正常的!发送确认 + 持久化 + 手动 ack 三件套缺一不可
以为 MQ 能保证不重复绝大多数 MQ 是”至少一次”投递——重复是必然的,必须自己实现幂等
以为 MQ 天然保序多分区/多队列 + 重试 = 乱序。需要顺序就别扩分区
自动 ack 直接干活自动 ack = 收到就确认。消费者处理到一半挂了,消息已”确认”,永远丢了
所有场景都用 MQMQ 不是银弹。加了 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:消息队列中转站,生产扔了消费捡;

解耦异步削峰三,缓冲池里稳如堰;

点对抢红订阅号,一条众人皆可见;

选型看场景,丢重乱积四道关。

相关链接