本轮 消息中间件面试题库(带答案)
生成时间:2026-05-22 23:03
【面试题】1. Kafka 为什么要把 Topic 分成多个 Partition?分区数量怎么规划?
【标准答案】 这个问题考查的是 Kafka 的核心架构设计。
为什么分区?三个关键原因:
第一,并行处理能力。单台 Broker 的吞吐量有限,分区可以分布在不同 Broker 上,消费者组内多个 Consumer 可以并行拉取,实现水平扩展。一个 Topic 的吞吐量 = 单个分区吞吐量 × 分区数。
第二,负载均衡。Producer 把消息按分区策略分发到各分区,避免单点热点。比如订单 Topic,按订单 ID 哈希分区,不同订单天然分散。
第三,容错和高可用。每个分区有多个副本,Leader 挂了 Follower 可以接管,故障恢复粒度是分区级别,不影响整个 Topic。
分区数量怎么规划? 我总结一个实用公式:
分区数 ≥ 目标吞吐量 ÷ 单分区极限吞吐量 × 冗余系数(1.2~1.5)举个例子:订单系统峰值 10 万 QPS,单分区实测 2 万 QPS,那至少需要 5 个分区,考虑冗余和未来增长,设 8-10 个更稳。
但要注意:分区不是越多越好!
- 分区越多,Broker 内存压力越大(每个分区都有 LogSegment、索引文件)
- 分区越多,故障恢复时间越长(需要选举的分区多了)
- 分区越多,Consumer Group 重平衡成本越高
实际经验是:中小规模系统 10-50 个分区够用,大规模系统单 Topic 不要超 100 分区,除非你清楚代价。
【面试题】2. RabbitMQ 的消息确认机制有哪些?什么情况下用 ACK、NACK、Reject?
【标准答案】 这道题考查 RabbitMQ 消息可靠性保障的核心机制。
三种确认方式的区别:
ACK(确认):
- 消费者处理成功后调用
basicAck - Broker 从队列删除消息,认为消费完成
- 这是最常用的正常流程
NACK(否定确认):
- 消费者处理失败但消息还有价值时调用
basicNack - 可以指定
requeue=true让消息重新入队,再次被消费 - 适合临时故障(如数据库连接超时),重试可能成功的场景
basicNack可以批量拒绝多条消息(RabbitMQ 扩展特性)
Reject(拒绝):
- 消费者无法处理时调用
basicReject - 类似 NACK 但只能拒绝单条消息
- 同样可以
requeue=true重新入队 - 或者配置死信队列,让消息进入 DLX 做特殊处理
实际场景举例:
- 订单支付成功消息 → 正常处理 →
basicAck - 积分服务临时不可用 → 可重试场景 →
basicNack(requeue=true) - 消息格式错误/业务逻辑异常 → 无法处理的脏数据 →
basicReject+ 配置 DLX,进死信队列人工排查
一个坑:自动确认 vs 手动确认
- 自动确认(
autoAck=true):消息投递即删除,消费者挂了消息就丢了 - 手动确认:消费者显式 ACK 才删除,更可靠
生产环境务必用手动确认,否则 RabbitMQ 的可靠性保障就废了。
【面试题】3. RocketMQ 的延迟消息是怎么实现的?为什么比 RabbitMQ 的延迟队列更高效?
【标准答案】 这道题对比两个主流 MQ 的延迟消息实现差异。
RocketMQ 的延迟消息实现:
RocketMQ 内置了 18 个延迟等级,不是任意延迟时间,而是固定时间槽:
1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h核心机制:
- Producer 发送延迟消息时,指定延迟等级(如 level=5 表示 1 分钟)
- Broker 收到后,先写入 CommitLog,但把消息的 Topic 替换为内部延迟 Topic:
SCHEDULE_TOPIC_XXXX - 每个延迟等级对应一个 ConsumeQueue,后台定时任务(类似时间轮)轮询
- 到期后,定时任务把消息从延迟 Topic 恢复到原始目标 Topic
- Consumer 正常消费
为什么高效?
- 延迟消息不阻塞正常消息,所有消息顺序写 CommitLog,利用顺序写高吞吐
- 时间轮算法,O(1) 复杂度管理大量延迟消息
- 不需要额外存储,复用 CommitLog 和 ConsumeQueue 架构
对比 RabbitMQ 的延迟队列:
RabbitMQ 延迟队列依赖 TTL + DLX 组合:
- 消息设置 TTL,进入普通队列等待过期
- 过期后进入死信队列,Consumer 监听 DLX
问题:
- 每条消息单独设置 TTL,队列需要排序检查,O(n) 复杂度
- 大量延迟消息堆积时,TTL 检查性能下降明显
- 需要创建专门的 DLX 队列,架构复杂
总结:RocketMQ 延迟消息是原生支持,架构简洁高效;RabbitMQ 是组合方案,适合延迟消息不多的场景。百万级延迟消息场景首选 RocketMQ。
【面试题】4. Kafka Consumer 的位移(Offset)存在哪里?自动提交和手动提交怎么选?
【标准答案】 这道题问的是 Kafka 消费进度的管理机制。
Offset 存储位置:
早期版本(0.9 之前)Offset 存在 Zookeeper,后来改到 Kafka 内部 Topic:__consumer_offsets。
为什么改?Zookeeper 写压力大,而且 Kafka 自己管理更可控。现在 __consumer_offsets 是 50 个分区,通过 Consumer Group ID 哈希决定存哪个分区。
自动提交 vs 手动提交:
| 维度 | 自动提交 | 手动提交 |
|---|---|---|
| 配置 | enable.auto.commit=true | enable.auto.commit=false |
| 提交时机 | 定期(默认 5 秒)自动提交 | 业务代码显式调用 commitSync/commitAsync |
| 可靠性 | 可能丢消息、可能重复 | 精确控制,可靠性高 |
| 吞吐量 | 高,省去提交开销 | 略低,但可控 |
自动提交的问题:
假设自动提交间隔 5 秒,Consumer 拉取一批消息处理了 3 秒就挂了:
- 已提交的 Offset 可能超前实际处理进度
- 重启后从已提交 Offset 继续,中间的消息丢了
反之,Consumer 处理完但还没到提交时间就挂了:
- Offset 没提交,重启后重复消费
手动提交的场景:
高可靠性场景必须用手动提交,比如:
- 订单支付消息:不能丢,不能重复扣款
- 数据同步消息:精确控制消费进度
最佳实践:
// 同步提交:处理完一批,提交一次,可靠性优先
consumer.commitSync();
// 异步提交:吞吐量优先,配合回调处理失败
consumer.commitAsync((offsets, exception) -> {
if (exception != null) {
log.error("提交失败", exception);
}
});我的建议:
- 业务消息:手动提交 + 幂等性保障,重复消费也能处理
- 日志收集、监控数据:自动提交可接受,偶尔丢几条无所谓
- 最关键的一点:Offset 提交策略要和业务幂等设计配合使用
【面试题】5. 消息队列如何保证消息不丢失?生产端、Broker 端、消费端分别怎么做?
【标准答案】 这道题是面试高频题,考查消息可靠性的全链路保障。
生产端保障:
RabbitMQ:
- 开启 Confirm 模式:
channel.confirmSelect() - 同步确认:每发一条等
waitForConfirms(),可靠但慢 - 异步确认:
addConfirmListener批量确认,性能好
- 开启 Confirm 模式:
Kafka:
acks=all(或acks=-1):Leader 和所有 ISR 副本都确认才算成功- 配合
retries设置重试次数 enable.idempotence=true:启用幂等生产者,避免重试导致重复
RocketMQ:
- 同步发送:
send()返回 SendResult 才算成功 - 异步发送:提供回调函数,失败有补救
- 单向发送(one-way):不保证可靠性,适合日志场景
- 同步发送:
Broker 端保障:
消息持久化:
- RabbitMQ:队列和消息都要
durable=true,消息设置DeliveryMode=2 - Kafka:
log.flush.interval.messages控制刷盘,默认依赖 PageCache
- RabbitMQ:队列和消息都要
副本机制:
- Kafka:每个分区至少 3 个副本,
min.insync.replicas=2,至少 2 个副本写入成功 - RocketMQ:主从同步复制,同步模式
syncMaster
- Kafka:每个分区至少 3 个副本,
硬件层面:
- RAID 磁盘阵列
- SSD 提升刷盘性能
- 不完全依赖 PageCache,关键业务配置同步刷盘
消费端保障:
手动确认:
- RabbitMQ:手动
basicAck,处理完再确认 - Kafka:手动提交 Offset,
enable.auto.commit=false - RocketMQ:
CONSUME_SUCCESS才算消费完成
- RabbitMQ:手动
幂等性设计:
- 消息带唯一 ID(如订单号)
- 消费前查 Redis/数据库去重
- 业务逻辑支持重复执行不影响结果
一个典型坑:
很多团队配置了持久化,但 Broker 挂了重启,消息还是丢了。原因是:
- 消息写入 PageCache 还没刷盘就宕机
- 或者副本同步是异步的,Leader 挂了数据在 Follower 还没同步
解决方案:
- Kafka:
acks=all+min.insync.replicas>1+unclean.leader.election.enable=false - RabbitMQ:镜像队列或仲裁队列,同步复制
总结一句话:可靠性是性能的代价,全链路保障缺一不可。
【面试题】6. Kafka 的 ISR 是什么?Leader 选举和 ISR 有什么关系?
【标准答案】 这道题考查 Kafka 的高可用核心机制。
ISR 定义:
ISR(In-Sync Replicas)是"同步副本集合",包含所有与 Leader 保持同步的副本(包括 Leader 自己)。
每个 Partition 的 ISR 列表由 Leader 维护,动态变化:
- Follower 及时同步数据 → 加入 ISR
- Follower 延迟超过阈值 → 踢出 ISR
判断标准:replica.lag.time.max.ms(默认 30 秒),Follower 超过这个时间没追上 Leader,就踢出 ISR。
ISR 的作用:
当 Leader 挂了,Controller 从 ISR 中选一个 Follower 成为新 Leader。这保证了新 Leader 的数据至少不落后旧 Leader 太多。
如果 ISR 为空怎么办?
两种情况:
unclean.leader.election.enable=false(默认):ISR 为空,分区不可用,直到 ISR 中有副本恢复unclean.leader.election.enable=true:允许从非 ISR 副本选 Leader,但可能丢数据
生产环境默认设 false,宁可短暂不可用也不要丢数据。
ISR 与生产者的关系:
当 Producer 设置 acks=all(或 -1),Leader 等待 ISR 所有副本都确认写入才返回成功。
配合 min.insync.replicas 参数(最小 ISR 数量),比如设为 2:
- ISR 有 3 个副本 → 至少 2 个确认成功
- ISR 只剩 1 个 → 抛异常,Producer 知道写入不安全
实际经验:
生产环境推荐配置:
- 副本数 ≥ 3
min.insync.replicas=2unclean.leader.election.enable=falseacks=all
这样保证:即使 1 台 Broker 挂了,数据依然有两份,不丢消息。
【面试题】7. 消息队列如何保证消息顺序性?全局有序和分区有序怎么实现?
【标准答案】 这道题考查消息顺序性保障,是分布式系统的经典难题。
首先明确:严格的全局有序和高吞吐是矛盾的。
消息队列的顺序性分三个层次:
1. 全局有序: 整个 Topic 的所有消息严格按发送顺序消费。
实现方式:
- 单分区、单消费者
- 性能瓶颈明显,吞吐量受限
适用场景:订单状态流转、账户余额变更等强顺序要求。
2. 分区有序: 同一分区内消息有序,不同分区无序。
Kafka 实现:
- Producer 发送时指定分区键(如订单 ID)
- 相同订单的消息进入同一分区
- Consumer 按分区顺序消费
RocketMQ 实现:
- 发送时指定 MessageQueueSelector
- 相同业务 ID 的消息进入同一队列
3. 应用层有序: 多个分区,但业务层面保证顺序。
实现方式:
- 消息带序列号或版本号
- Consumer 维护每个业务 ID 的最后处理序号
- 乱序到达的消息缓存等待,超过序号的先缓存
生产环境的坑:
即使配置了分区有序,实际还可能乱序:
Producer 重试: 第一条消息失败重试,第二条成功先发送,顺序颠倒
- 解决:
max.in.flight.requests.per.connection=1,限制并发请求数
- 解决:
Consumer 多线程: 同一分区消息被多线程并发处理
- 解决:单线程消费,或用内存队列+单线程分发
Consumer Group 重平衡: 分区重新分配,Offset 回退或跳跃
- 解决:重平衡期间暂停处理,或业务层去重兜底
实际经验:
大部分场景用分区有序就够了:
- 订单系统:订单 ID 做分区键,同一订单的状态变更消息保证顺序
- 用户消息流:用户 ID 做分区键,同一用户的推送有序
总结:全局有序牺牲吞吐,分区有序够用,应用层有序最灵活但开发成本高。
【面试题】8. RabbitMQ 如何实现高可用?镜像队列和仲裁队列有什么区别?
【标准答案】 这道题考查 RabbitMQ 集群高可用方案。
RabbitMQ 高可用架构:
经典方案是镜像队列(Classic Mirrored Queue),现在官方推荐仲裁队列(Quorum Queue)。
镜像队列:
工作原理:
- 主队列在 Master 节点
- 消息同步复制到 Slave 节点
- Consumer 从 Master 消费
- Master 挂了,Slave 提升为 Master
配置方式(策略):
rabbitmqctl set_policy ha-all "^order\." \
'{"ha-mode":"all","ha-sync-mode":"automatic"}'问题:
- 同步复制影响性能,写入要等所有 Slave 确认
- 网络分区时可能出现脑裂,数据不一致
- 镜像队列已标记为"未来移除"
仲裁队列(Quorum Queue):
RabbitMQ 3.8+ 引入的新方案:
- 基于 Raft 共识算法
- 默认需要仲裁多数节点(如 3 节点需 2 节点确认)
- 天然防脑裂,数据一致性更强
创建方式:
Map<String, Object> args = new HashMap<>();
args.put("x-queue-type", "quorum");
channel.queueDeclare("order", true, false, false, args);核心区别对比:
| 维度 | 镜像队列 | 仲裁队列 |
|---|---|---|
| 一致性 | 最终一致(异步复制可能丢数据) | 强一致(Raft 保证) |
| 性能 | 较高,异步复制 | 略低,同步复制但优化了 |
| 脑裂处理 | 可能脑裂,需要人工干预 | Raft 自动选主,无脑裂 |
| 运维复杂度 | 需要监控分区告警 | 配置简单,自恢复 |
| 官方态度 | 逐步淘汰 | 推荐方案 |
我的建议:
新项目直接用仲裁队列,除非:
- RabbitMQ 版本低于 3.8
- 极致性能要求,能接受小概率丢数据
老项目迁移:
- 先升级到 RabbitMQ 3.13+
- 逐步替换关键队列为仲裁队列
- 非关键队列可以保留镜像队列
集群规模建议:
- 3 节点仲裁队列(2 节点同意即可)
- 5 节点(3 节点同意)生产环境推荐
- 跨机房部署时注意网络延迟
【面试题】9. 消息积压了怎么处理?线上紧急情况怎么快速恢复?
【标准答案】 这道题是真实生产场景的高频问题,考查故障处理能力。
消息积压的原因:
- Consumer 处理变慢(数据库慢查询、第三方接口超时)
- Consumer 挂了,积压在队列
- Consumer 扩容了,但分区数不够,无法并行
- Producer 突发流量洪峰,Consumer 处理不过来
线上紧急处理四步走:
第一步:快速止血,恢复服务
如果是 Consumer 挂了:
- 重启 Consumer,先恢复消费
- 如果是代码 Bug,紧急回滚或热修复
如果是数据库压力太大:
- Consumer 临时暂停消费,保护下游
- 先解决数据库瓶颈(扩容、索引优化)
第二步:临时扩容,加速消费
Kafka 场景:
- 问题:分区数有限,Consumer 数不能超分区数
- 方案:临时创建新 Topic,分区数扩 10 倍
- 用临时 Consumer 把积压消息转发到新 Topic
- 新 Topic 用更多 Consumer 并行消费
RocketMQ 场景:
- 直接增加 Consumer 实例(不限于分区)
- 调整消费线程池大小
RabbitMQ 场景:
- 增加 Prefetch Count,提高单 Consumer 吞吐
- 临时加 Consumer 实例
第三步:业务降级,保护核心
如果消费速度还是跟不上:
- 非关键业务消息临时丢弃或延迟处理
- 核心业务优先处理(如支付消息优先于通知消息)
- 写入文件或数据库暂存,后续批量回补
第四步:根因分析,防止复发
处理完紧急情况后:
- Consumer 性能优化(异步处理、批量入库)
- 数据库慢查询治理
- 监控告警完善(积压量超阈值立即告警)
- 演练预案,团队熟悉处理流程
一个真实案例:
某电商大促,订单消息积压 500 万条。
处理过程:
- Consumer 挂了 → 重启,但数据库撑不住
- 临时方案:Consumer 改为写文件,不动数据库
- 积压消息导出,离线批量入库
- 入库完成后再消费新消息
- 大促后优化数据库索引,下次压测验证
预防措施:
- 压测演练:模拟积压场景,验证恢复方案
- 容量规划:Consumer 吞吐量 × 1.5 倍冗余
- 监控告警:积压量超阈值立即通知
- 限流降级:Producer 端限流,保护 Consumer
【面试题】10. RabbitMQ、Kafka、RocketMQ 怎么选?各自适合什么场景?
【标准答案】 这道题是架构选型必问,考查综合判断能力。
核心对比表格:
| 维度 | RabbitMQ | Kafka | RocketMQ |
|---|---|---|---|
| 吞吐量 | 万级 QPS | 百万级 QPS | 十万级 QPS |
| 延迟 | 微秒级 | 毫秒级 | 毫秒级 |
| 消息可靠性 | 高(镜像队列/仲裁队列) | 高(副本+ISR) | 高(主从同步) |
| 功能丰富度 | 极高(多种 Exchange、路由) | 中等(偏日志场景) | 高(延迟、事务消息) |
| 顺序性保障 | 单队列有序 | 分区有序 | 队列有序 |
| 事务消息 | 不支持 | 支持(Kafka 事务) | 支持(原生半消息) |
| 延迟消息 | TTL+DLX 组合实现 | 不支持(需外挂) | 原生支持 18 等级 |
| 社区生态 | 成熟,资料多 | 最活跃,生态最强 | 国内生态好 |
| 学习曲线 | 较低 | 中等 | 中等 |
| 运维复杂度 | 较低 | 高(依赖 Zookeeper) | 中等 |
选型建议:
选 RabbitMQ 的场景:
- 业务复杂路由需求(多种 Exchange 类型)
- 企业内部系统,QPS 不高(<5 万)
- 需要延迟队列但量不大
- 团队熟悉 Erlang 或不想学新技术栈
- 典型案例:订单系统内部消息流转、用户通知推送
选 Kafka 的场景:
- 大数据量日志收集、实时数据管道
- 百万级吞吐场景
- 需要与大数据生态集成(Flink、Spark、Hadoop)
- 流式计算场景(Kafka Streams)
- 典型案例:用户行为日志收集、实时推荐系统
选 RocketMQ 的场景:
- 金融交易场景,需要事务消息
- 延迟消息需求(定时任务调度)
- 国内团队,中文文档友好
- 电商订单、支付消息
- 典型案例:订单支付回调、优惠券发放、库存扣减
组合使用也很常见:
大型系统分层设计:
- 业务消息 → RocketMQ(事务、延迟消息)
- 日志收集 → Kafka(高吞吐)
- 内部通知 → RabbitMQ(灵活路由)
我的经验:
问三个问题做决策:
- 吞吐量多少?>10 万 QPS → 排除 RabbitMQ
- 需要事务消息吗?是 → RocketMQ 或 Kafka
- 团队熟悉什么?学习曲线也是成本
没有最好的 MQ,只有最适合的。先明确业务需求,再选技术方案。
本轮题库生成完毕,共 10 道面试题。