消息队列是什么
这一课你将能做到
- 画出「同步 HTTP 调用」与「异步发事件」两条链路,指出用户等待边界各落在哪一步
- 用 Topic 和 Consumer Group 两个词解释:三个下游服务如何各自完整消费同一批订单事件
- 对「缓存失效广播 / 订单多下游处理 / 改昵称」三类场景,判断该用 Redis、Kafka 还是只写 MySQL,并各写一句理由
为什么要学
下单后要发通知、加积分、同步搜索索引。若在下单接口里串行 HTTP 调用三个服务,任何一个慢了或挂了,整笔订单接口就被拖垮:用户盯着转圈,接口 P99 飙升,Nginx 上的超时和重试开始堆积。
消息队列(Message Queue,MQ)把「发生了什么」写成一条消息交给中间件,下游各服务按自己的节奏处理。用户只等「订单写入 MySQL 成功」,发通知、加积分这些事后消化。真实开发里它解决三件事:
- 解耦:一次动作多个下游要响应,服务之间不再互相依赖对方的部署与可用性
- 削峰:秒杀、批量导入的写入尖峰先落进日志,下游按自身能力匀速消费
- 可回溯:事件按 offset 保留一段时间,出了问题能重放历史修数据
面试层面,MQ 是 Java 后端必考题:为什么用 MQ、引入 MQ 的代价(重复消费、消息丢失、顺序性)、Kafka 与 RabbitMQ 怎么选。本 track 后续 11 课会把这些问题逐个变成你能动手验证的实验。
人话:MQ 是服务之间的「留言板」——写的人写完就走,读的人稍后自己来取,双方互不等待。
先建立图像
同步(紧耦合):
用户 → 下单 API ──HTTP──► 库存服务
──HTTP──► 通知服务
──HTTP──► 积分服务
任一慢/挂 → 用户一直等,整单失败
异步(解耦):
用户 → 下单 API → MySQL 事务提交 → 发 OrderCreated 到 Topic
│
┌──────────────────┼──────────────────┐
▼ ▼ ▼
通知服务(组A) 积分服务(组B) 搜索服务(组C)
各组各自记 offset,互不抢对方进度
三种「传话」方式对比:
| 需求 | Redis Pub/Sub | Kafka Topic |
|---|
| 消费者离线后再补历史 | ❌ 当时不在线就丢 | ✅ 日志按 offset 回溯 |
| 多实例竞争分摊 | ❌ 广播给所有订阅者 | ✅ 同一 Consumer Group 分摊分区 |
| 高吞吐事件流 | 轻量通知够用 | 设计目标就是追加写日志 |
| 本站典型用途 | 缓存失效广播 | 订单/审计/跨服务事件 |
[spring-boot API] --produce--> Topic: order-events (多分区)
│
Consumer Group notify │ Consumer Group audit
(组内互斥分摊) │ (另一组完整再读一遍)
跟着做
步骤 1:钉死「用户等什么」
打开笔记写两行:
- 同步:用户等到「库存 + 通知 + 积分」全部返回才拿到 200
- 异步:用户等到「订单行写入 MySQL 成功」就拿到 200,通知由后台消费发出
结论写一句话:异步不是「不做通知」,而是把通知移出请求临界路径。
步骤 2:画用户注册的两条时序
- 同步:API → 写用户表 → HTTP 发欢迎邮件 → HTTP 记埋点统计
- 异步:API → 写用户表 → produce
UserRegistered → 邮件组 / 统计组各自消费
异步图上必须标出 Topic 名(如 user-events)与两个不同的 group.id。应看到:同一 Topic、两个组 = 两条独立订阅线,不是「抢一条消息」。
步骤 3:用代码体会「异步边界」
先不碰 Kafka,在任意 Spring Boot 项目里模拟:
@PostMapping("/sync-order")
public String syncOrder() {
try {
Thread.sleep(2000); // 模拟调用通知服务慢
Thread.sleep(1000); // 模拟调用积分服务慢
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "ok"; // 用户等了 3 秒才拿到响应
}
把这个接口改成「写库成功即返回,其余交给后台消费者」,你就完成了本课最重要的判断:哪些步骤属于用户临界路径,哪些属于事后处理。判断口诀——这步失败用户能否接受稍后完成或人工补偿?能 → 异步;不能(如扣款、扣库存)→ 同步事务内做。
步骤 4:读官方定位一段
步骤 5:场景分流三连
对每个场景写「Redis / Kafka / 只 MySQL」+ 一句理由:
- 课程详情页缓存过期后的失效广播
- 订单创建后通知、审计、搜索三方都要处理
- 用户改昵称写
cf_user
参考方向:失效广播偏 Redis(瞬时、丢了无所谓);订单多下游偏 Kafka(要历史、要多订阅者);改昵称权威写 MySQL(单一真相源)。
步骤 6:看一条事件长什么样
{
"event": "OrderCreated",
"eventId": "01JBEXAMPLEUUID",
"orderId": "1001",
"userId": "u42",
"amount": 9900,
"occurredAt": "2026-10-06T00:00:00Z"
}
消息是「发生了什么」的快照,不是订单表本身;字段设计下一阶段的「消息格式」课展开。
讲清楚要点
- MQ 的本质是「把时间上必须紧挨着的两件事拆开」:生产方只负责把事件写进日志,消费方按自己的节奏读。拆开后两侧可以独立扩容、独立失败、独立部署。
- Kafka 的消息是「读不删」的日志:与队列「领走即删」不同,消息保留在分区里一段时间,多个消费组可以各自从头读——这决定了它天然适合多订阅者与重放。
- 用户等待边界由你定义:临界路径内只放「必须同步完成」的事(事务写库),其余进事件流。边界画错,要么接口变慢,要么业务丢步骤。
- 组内互斥分摊、组间完整重读:通知组与审计组用不同
group.id,一条消息两边各处理一次;这跟「两个服务抢一条消息」是两回事。
- 业务真相仍在 MySQL:消息发送成功 ≠ 业务成功。Kafka 存事件副本,修复下游靠重放,改账仍走数据库事务。
- 不要为了「架构先进」上 MQ:小流量单体用同步 REST + 事务最简单;MQ 一旦引入,重复、乱序、积压、监控都是新增成本,必须换得回收益。
你可能会懵的地方
-
「异步 = 不可靠?」
不是。可靠性靠 Producer acks、重试、消费幂等与 lag 监控(后续课逐个落实)。用户看到的「成功」应对齐数据库提交,通知属于最终一致——延迟通常毫秒到秒级。
-
「一个 Topic 多个服务会抢消息吗?」
同一组内才会互斥分摊;不同 group.id 各有独立 offset,同一条事件可以被通知组与审计组各读一次。
-
「有了 Redis 还要 Kafka?」
Redis Pub/Sub 适合瞬时通知(缓存失效),不落历史;Kafka 适合要历史、要分摊、要多订阅者的事件流。两者职责不同,不是替代关系。
-
「微服务必须上 Kafka?」
不是。小团队同步 REST/Feign 完全够;出现多下游、流量尖峰、需要重放时再上,收益才盖过运维成本。
-
「消息丢了怎么办?」
先问丢在哪一段:生产侧(acks 配置)、Broker 侧(副本与刷盘)、消费侧(提交时机)。三段各有对策,本 track 的「生产者」「投递语义」「监控」三课会逐段拆解。
错法对照
| 错法 | 问题 | 正解 |
|---|
| 下单接口里同步调通知/积分/搜索,任一超时整单失败 | 下游故障被打进用户临界路径 | 事务写订单成功后发 OrderCreated,下游独立消费 |
| 用 Redis Pub/Sub 存订单事件,期望离线消费者补数据 | Pub/Sub 无历史,错过即丢 | 订单类事件进 Kafka,按 offset 回溯 |
两个服务共用一个 group.id 却期望各收全量 | 变成竞争消费,互相抢分区 | 通知与审计用不同 group.id |
| 认为「Kafka 发送成功 = 下单成功」 | Broker 确认只代表写入日志 | MySQL 事务成功才算业务成功,消息是旁路事件 |
| 为「架构完整」给单体站点强行上 Kafka | 运维成本远超收益 | 主路径 MySQL + Redis,Kafka 作为扩展实验 |
卡点急救
- 分不清「什么该异步」:拿业务步骤逐条问「这步失败,用户能否接受稍后完成或人工补偿?」能 → 异步;不能(扣款、扣库存、发凭证)→ 留在同步事务内。
- 担心消息丢导致业务断链:先做「对账兜底」——定时任务扫订单表,把「已支付但未发通知」的行补发事件;消息系统是加速器,对账是安全网,两者都要有。
- 团队没人用过 Kafka 不敢上:先按下一课的 Docker 方案把链路跑通(约 10 分钟),用实验数据支撑选型争论,比空对空讨论架构图有效得多。
面试常问
-
为什么要用消息队列?
背三个词:解耦(生产者不依赖消费者存活)、异步(下游耗时移出用户临界路径)、削峰(尖峰先入日志,下游匀速消费)。同时主动补一句代价:引入重复消费、消息丢失、顺序性、可用性四大问题,需要幂等与监控配套——能说出代价才显得真用过。
-
用了消息队列会有什么问题?
①重复消费:先处理再提交位点的 at-least-once 模型必然可能重复,靠业务幂等(唯一键/去重表)解决;②消息丢失:生产 acks、Broker 副本、消费提交时机三段都可能丢,各有配置对策;③顺序性:Kafka 只保证分区内有序,需按实体 key 路由;④复杂度:多一个需要监控(lag)与运维的组件。
-
为什么选 Kafka 而不是 RabbitMQ?
答需求形状而不是品牌:多订阅者、要重放历史、吞吐量大(日志追加、顺序写磁盘)选 Kafka;任务队列、灵活路由、延迟重试、死信开箱即用选 RabbitMQ;轻量瞬时通知用 Redis Pub/Sub。
自测三问
- 用户注册成功后要写库、发欢迎邮件、记埋点:画出同步与异步各 3 步,异步图上标 Topic 与两个 group。
- 缓存失效广播用 Redis、订单多下游用 Kafka:各写一条「不可替代」的理由。
- 同一 Topic 上 notify 与 audit 两个组同时跑,一条消息会被处理几次?谁决定「组内是否重复」?
小结一句话
消息队列把「紧挨着的两件事」拆成「写日志 + 读日志」:解耦、削峰、可重放;Redis 管轻通知,Kafka 管事件流,业务真相仍在 MySQL。
延伸
- Apache Kafka 官方介绍
- 下一课「核心概念」:Broker、Topic、Partition、Offset、Consumer Group 五个词是后面每一课的地基,一次钉死。