消息队列实战面试题
消息队列实战面试题(Redis / Kafka / RabbitMQ 等)
面向 Java 后端,强调原理 + 项目落地经验
1. 什么是消息队列?为什么要用消息队列?
口述版:
消息队列是一种异步通信中间件,生产者把消息发送到队列,消费者从队列中取消息处理,实现系统解耦。
主要解决三个问题:
- 异步处理:提高响应速度。比如用户下单后,发短信、扣积分、写日志等非核心操作放到队列异步执行,接口快速返回。
- 系统解耦:上游不需要知道下游有谁,新增消费者不需要改生产者代码。
- 削峰填谷:应对流量突发。秒杀、活动时大量请求先进队列,后端按自己节奏消费,保护数据库。
其他好处还有:最终一致性、广播通知、日志收集等。
2. 常见消息队列有哪些?怎么选型?
口述版:
| MQ | 特点 | 适用场景 | 缺点 |
|---|---|---|---|
| Kafka | 高吞吐、持久化、分区有序 | 日志收集、大数据、高并发业务 | 运维复杂,延迟相对较高 |
| RabbitMQ | 功能丰富、延迟低、支持多种协议 | 业务解耦、对可靠性要求高 | 吞吐不如 Kafka |
| RocketMQ | 阿里开源,支持事务消息、延迟消息 | 电商、金融业务 | 社区相对 Kafka 小 |
| Redis | 轻量、延迟极低 | 简单异步、实时通知、轻量队列 | 消息可靠性弱,不适合核心业务 |
| Pulsar | 云原生、存储计算分离 | 新一代大规模场景 | 相对较新 |
选型建议:
- 日志、大数据、超高吞吐 → Kafka
- 业务系统、需要灵活路由、延迟消息 → RabbitMQ 或 RocketMQ
- 只是简单异步、对丢失不敏感 → Redis(List 或 Stream)
- 国内互联网公司业务系统现在很多用 RocketMQ 或 Kafka。
3. Kafka 的基本架构讲一下
口述版:
Kafka 核心概念:
- Producer:生产者,发送消息。
- Broker:Kafka 服务器节点。
- Topic:消息主题,逻辑分类。
- Partition:分区,一个 Topic 可分为多个 Partition,实现并行和扩展。
- Consumer:消费者。
- Consumer Group:消费者组,同一组内消费者共同消费一个 Topic,实现负载均衡。
- Offset:消费者消费到的位置,记录在
__consumer_offsets或外部存储。
消息写入时按 Partition 顺序追加到磁盘日志文件(顺序写,性能高)。
消费者通过 Offset 记录进度,支持从指定位置重新消费。
关键点: 同一 Partition 内消息有序,不同 Partition 之间不保证全局有序。
4. Kafka 如何保证消息不丢失?
口述版:
要从生产端、Broker、消费端三方面一起保证:
1. 生产端:
acks=all(或 -1):等所有 ISR 副本都确认才认为发送成功。retries设合理重试次数。- 使用
idempotence=true(幂等生产者)防止重试导致重复。
2. Broker 端:
- 设置合理的副本数(
replication.factor >= 3)。 min.insync.replicas >= 2,保证至少有几个副本写入成功。- 关闭 unclean leader election(防止数据丢失的副本成为 Leader)。
3. 消费端:
- 手动提交 Offset(
enable.auto.commit=false)。 - 业务处理成功后再提交 Offset。
- 处理好异常重试和死信队列。
三者配合,才能最大程度保证「消息不丢」。
5. Kafka 如何保证消息顺序?
口述版:
Kafka 只保证单个 Partition 内的消息有序,不保证 Topic 全局有序。
实现顺序的常用做法:
- 单 Partition:简单但失去并行能力,吞吐低。
- 按业务 Key 分区:发送时指定相同的 key(如 orderId、userId),相同 key 的消息进入同一个 Partition,从而保证局部有序。
- 消费端单线程消费某个 Partition,或在业务上做顺序控制。
实际项目中,大多数业务只需要「局部有序」(比如同一个订单的状态变更有序),而不是全局有序。
6. 如何处理消息重复消费?(幂等性)
口述版:
消息队列通常是 至少一次(at-least-once) 语义,所以重复消费几乎不可避免,必须在业务层做幂等。
常见手段:
- 唯一业务 ID + 数据库唯一索引:插入前检查或靠唯一约束防重。
- Redis 去重:用消息唯一 ID 做 setnx,处理过就跳过。
- 状态机:根据业务状态判断是否已处理(如订单已支付就不再处理支付消息)。
- 数据库乐观锁 / 版本号。
- Kafka 幂等生产者 + 事务(正好一次语义),但仍建议业务层再做一层幂等。
核心原则: 不要依赖 MQ 保证不重复,而是假设一定会出现重复,把消费逻辑设计成幂等的。
7. 消费者提交 Offset 的时机?自动提交有什么问题?
口述版:
- 自动提交(
enable.auto.commit=true):按时间间隔自动提交。问题是:可能业务还没处理完就提交了 Offset,导致消息丢失;或者处理完但还没提交就挂了,导致重复消费。 - 手动提交(推荐):
- 业务处理成功后再调用
commitSync()或commitAsync()。 - 同步提交更可靠,异步性能更好,可结合使用。
- 业务处理成功后再调用
最佳实践:关闭自动提交,处理完业务再手动提交,并配合幂等设计。
8. 什么是死信队列?怎么处理?
口述版:
死信队列(Dead Letter Queue, DLQ)用来存放无法被正常消费的消息(重试多次仍失败、格式错误、业务拒绝等)。
作用:
- 防止坏消息一直阻塞消费。
- 方便后续排查和人工处理。
处理流程通常是:
- 消费失败后重试(可设置重试次数和间隔)。
- 超过重试次数后,把消息转发到死信队列。
- 监控死信队列,告警并人工介入(修复数据后重新投递,或记录后丢弃)。
Kafka 本身没有内置 DLQ,需要业务或中间件(如 Spring Kafka、自研)实现;RabbitMQ、RocketMQ 对死信支持更完善。
9. Redis 可以做消息队列吗?怎么实现?有什么局限性?
口述版:
可以做轻量级消息队列,常见三种方式:
- List:
LPUSH+BRPOP(阻塞弹出),实现简单队列。 - Pub/Sub:发布订阅,实时性好,但消息不持久化,消费者不在线会丢消息。
- Stream(Redis 5.0+):支持消费者组、ACK、持久化,更接近专业 MQ。
局限性(重要):
- 消息可靠性不如专业 MQ(持久化、副本机制弱于 Kafka)。
- 不适合超大数据量堆积。
- 功能单一(事务消息、延迟消息、死信等需要自己实现)。
- 内存成本较高。
适用场景: 实时性要求高、消息量不大、允许少量丢失的场景(如实时通知、简单异步任务)。核心业务消息建议用 Kafka / RocketMQ。
10. 如何实现延迟消息 / 延时队列?
口述版:
不同 MQ 实现方式不同:
- RocketMQ:原生支持延迟消息(固定延迟级别或任意时间)。
- RabbitMQ:通过死信队列 + TTL 实现,或使用延迟插件。
- Kafka:本身不支持,常见做法:
- 时间轮 + 本地延迟(业务自己实现)。
- 使用 Redis ZSet 存到期时间,定时扫描投递到 Kafka。
- 使用专门的延迟队列中间件。
- Redis:用 ZSet,score 存执行时间戳,后台线程轮询到期任务。
项目中如果只是简单延迟,用 Redis ZSet 很常见;对可靠性要求高时优先考虑 RocketMQ。
11. Kafka 消费者 Rebalance 是什么?会有什么问题?
口述版:
Rebalance(再均衡)是 Consumer Group 内消费者数量变化、或 Topic 分区变化时,重新分配分区的过程。
触发时机:
- 新消费者加入 / 老消费者退出
- 消费者心跳超时(被踢出)
- Topic 分区数变更
问题:
- Rebalance 期间消费者会停止消费,造成短暂的消费暂停(Stop-The-World)。
- 频繁 Rebalance 会导致消费延迟上升、Offset 提交异常。
优化手段:
- 合理设置
session.timeout.ms和heartbeat.interval.ms。 - 避免频繁上下线消费者。
- 使用 Cooperative Sticky 分配策略(减少停止消费的范围)。
- 业务处理时间不要过长,防止被判定超时。
12. 消息积压怎么处理?
口述版:
消息积压是线上常见问题,排查和处理思路:
-
先看原因:
- 消费者处理太慢(业务逻辑重、DB 慢、下游接口慢)
- 消费者挂了或数量不够
- 生产者突发流量
- 分区数太少,并行度不够
-
紧急处理:
- 扩容消费者实例(水平扩展)。
- 临时增加分区(注意:分区只能增不能减,且对有序性有影响)。
- 优化消费逻辑(批量处理、异步化、减少 DB 交互)。
- 降级非核心消费逻辑。
-
长期方案:
- 监控 Consumer Lag(积压量)。
- 提前做好容量规划。
- 对高峰流量做限流或削峰。
Kafka 常用 kafka-consumer-groups.sh 或监控系统看 Lag。
13. 事务消息怎么实现?(以 RocketMQ / Kafka 为例)
口述版:
事务消息解决的是「本地事务和发消息」的原子性问题(比如扣库存和发订单消息要么都成功,要么都失败)。
RocketMQ 半消息机制(最经典):
- 发送半消息(对消费者不可见)。
- 执行本地事务。
- 本地事务成功 → 提交消息;失败 → 回滚消息。
- 如果确认丢失,Broker 会回查本地事务状态。
Kafka 事务:
- 使用幂等生产者 + 事务 API(
transactional.id)。 - 可以做到「写入多个 Partition + 提交 Offset」的原子性,常用于 Exactly-Once 语义。
实际业务中,很多团队用「本地消息表 + 定时任务」实现最终一致性,简单可靠。
14. 项目中怎么保证消息的可靠性(端到端)?
口述版(实战回答模板):
我们从三个环节保证:
-
生产端:
- 关键消息同步发送 + 失败重试。
- 记录发送日志或使用本地消息表。
- Kafka 设置
acks=all+ 幂等。
-
Broker:
- 多副本 + ISR 机制。
- 监控副本同步状态。
-
消费端:
- 手动提交 Offset。
- 业务幂等(唯一键 / Redis / 状态机)。
- 失败重试 + 死信队列。
- 监控消费延迟和死信数量。
整体采用「至少一次 + 业务幂等」策略,而不是强依赖 MQ 的正好一次。
15. Redis Stream 和 Kafka 有什么区别?什么时候用 Redis Stream?
口述版:
| 对比点 | Redis Stream | Kafka |
|---|---|---|
| 定位 | 轻量流式数据结构 | 分布式高吞吐消息系统 |
| 持久化 | RDB/AOF,可靠性一般 | 强持久化 + 多副本 |
| 吞吐量 | 中等 | 非常高 |
| 消费者组 | 支持 | 成熟支持 |
| 运维复杂度 | 低(跟着 Redis) | 较高 |
| 适用规模 | 中小规模、实时性要求高 | 大规模、高可靠、日志场景 |
用 Redis Stream 的场景:
- 已经在用 Redis,不想引入新中间件。
- 消息量不大,对可靠性要求不是极端严格。
- 需要低延迟的实时流处理。
核心业务、大数据量、强可靠场景,优先选 Kafka 或 RocketMQ。
面试加分点
- 能结合自己项目讲:用了什么 MQ、为什么选它、怎么保证不丢不重、有没有遇到过积压或 Rebalance 问题。
- 清楚「至少一次 + 幂等」是主流可靠方案。
- 能说清 Kafka 分区有序、Offset 提交时机、死信处理。
- 知道 Redis 做队列的局限,不把核心业务消息只放在 Redis。
- 对事务消息、延迟消息有落地思路。
快速记忆框架:
消息队列面试核心:
为什么用 → 选型对比 → Kafka 架构与可靠性 → 顺序性 → 重复消费(幂等) → Offset 提交 → 死信与积压 → 延迟消息 → Redis 做队列的取舍
把「不丢、不重、有序、积压处理」这四块讲清楚,再结合项目经验,基本就能覆盖大部分实战面试。