← 返回分布式系统
📨 中间件 · 面试必问

消息队列 MQ

消息队列用一句话概括:把"现在就做"变成"稍后做",把"我直接调你"变成"你听广播"。它是解耦、异步、削峰的标配中间件。

🎯为什么需要消息队列

三个经典价值:解耦、异步、削峰。一句话——让系统"不那么同步、不那么紧绑、不那么怕洪峰"。

生产者 消息队列缓冲/解耦 消费者A 消费者B 消费者C
解耦:生产者不必知道谁消费,新增消费者无需改生产者。
异步:生产者发完即返回,耗时操作后台慢慢消费。
削峰:突发流量先堆在队列,消费者按自己节奏处理,保护后端。

🧩两种核心模型

① 点对点(Queue / 竞争消费)
一条消息只被一个消费者处理。多个消费者组成"消费组",抢着消费,天然做负载均衡。典型场景:订单处理、任务分发。
② 发布/订阅(Topic / 广播)
一条消息被所有订阅者收到。典型场景:事件通知、日志广播、缓存失效。Kafka 用"消费组"把两者统一:组内竞争、组间广播。
🔑
记忆:Queue=一个活一人干;Topic=一个活所有人收到。Kafka 的 Topic + Consumer Group 同时实现了这两种语义。

🧱核心工作模式

异步处理
注册成功后,发短信、发邮件、初始化画像等非核心链路,丢进 MQ 异步做,主流程秒回。
应用解耦
下单系统只发"订单已创建"事件,库存、积分、推荐各自订阅,互不依赖、独立扩容。
流量削峰
秒杀时请求先入队,后端按数据库承载能力匀速消费,避免被打垮。
消息驱动 / 事件溯源
把状态变更作为事件流,下游可重放(如 CDC 数据同步),也可做审计与回放。

🛡️可靠性:不丢、不重、不卡

投递语义(面试必考三级):
语义含义能否做到
At most once最多一次,可能丢易,但不安全
At least once至少一次,可能重复常见(Kafka 默认),配合幂等
Exactly once恰好一次,不丢不重难,需事务/幂等+去重(Kafka 事务、Flink 两阶段)
⚠️
生产者不丢:用acks=all(ISR 全收)+ 重试。
Broker 不丢:副本机制 + 落盘(fsync)。
消费者不丢:先处理业务再提交 offset,避免"消费中宕机 offset 已提交"导致消息丢失。

🔁幂等:消息重复怎么办

网络重试、Rebalance、acks=all 都会造成消息被消费多次。幂等保证"处理多次 = 处理一次"。

常见幂等方案

① 唯一键 + 数据库去重表(如订单号):插入前查,存在则跳过。

② 业务状态机:已支付→不再处理支付消息。

③ Redis SETNX / 原子锁:用 messageId 占位防重。

④ Kafka 幂等生产者:broker 端按 pid+seq 去重,避免生产者重试产生重复。

✅
口诀:"做不到 exactly-once,就做 at-least-once + 幂等"。这是工业界最务实的组合。

🔢顺序消费

很多业务要求"同一订单的消息按顺序处理"(如 创建→支付→发货)。

Kafka 的顺序保证:同一 partition 内消息有序;只要把同一业务键(如订单ID)哈希到同一 partition,且单 partition 单线程消费,即可保证全局顺序。
⚠️
代价:顺序消费会牺牲并行度(partition 内串行)。所以"要顺序"和"要高并发"要按业务键粒度权衡,通常只需"按订单顺序"而非"全局顺序"。

⚖️主流 MQ 选型对比

维度KafkaRocketMQRabbitMQ
定位高吞吐日志/流高可靠事务消息低延迟路由
吞吐量极高(百万级/s)高中(万级/s)
延迟毫秒~秒毫秒微秒~毫秒
顺序partition 级queue 级单队列
事务消息支持(事务API)强(半消息机制)弱
典型场景日志、埋点、流处理电商交易、金融后台任务、RPC替代
🧭
选型直觉:要吞吐/流处理 → Kafka;要事务/金融级可靠 → RocketMQ;要灵活路由/低延迟 → RabbitMQ。深入 Kafka 见 kafka-deep-dive。

🎯面试要点速记

三大价值:解耦、异步、削峰。
两种模型:点对点(竞争消费)+ 发布订阅(广播);Kafka 用 Topic+Group 统一。
投递语义:at-most / at-least / exactly once,业界多用 at-least-once + 幂等。
不丢消息:生产者 acks=all+重试、Broker 副本落盘、消费者先处理后提交 offset。
幂等方案:去重表 / 状态机 / Redis 原子锁 / Kafka 幂等生产者。
顺序:同 key 哈希到同 partition + 单线程消费。
🔥
必考题:"如何保证消息不丢失?"——分三段答:生产端(确认+重试)、服务端(多副本+刷盘)、消费端(先消费后提交位移)。漏掉任何一段都算不完整。