Skip to content

本轮 消息中间件面试题库(带答案) ​

生成时间: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 做特殊处理

实际场景举例:

  1. 订单支付成功消息 → 正常处理 → basicAck
  2. 积分服务临时不可用 → 可重试场景 → basicNack(requeue=true)
  3. 消息格式错误/业务逻辑异常 → 无法处理的脏数据 → 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

核心机制:

  1. Producer 发送延迟消息时,指定延迟等级(如 level=5 表示 1 分钟)
  2. Broker 收到后,先写入 CommitLog,但把消息的 Topic 替换为内部延迟 Topic:SCHEDULE_TOPIC_XXXX
  3. 每个延迟等级对应一个 ConsumeQueue,后台定时任务(类似时间轮)轮询
  4. 到期后,定时任务把消息从延迟 Topic 恢复到原始目标 Topic
  5. 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=trueenable.auto.commit=false
提交时机定期(默认 5 秒)自动提交业务代码显式调用 commitSync/commitAsync
可靠性可能丢消息、可能重复精确控制,可靠性高
吞吐量高,省去提交开销略低,但可控

自动提交的问题:

假设自动提交间隔 5 秒,Consumer 拉取一批消息处理了 3 秒就挂了:

  • 已提交的 Offset 可能超前实际处理进度
  • 重启后从已提交 Offset 继续,中间的消息丢了

反之,Consumer 处理完但还没到提交时间就挂了:

  • Offset 没提交,重启后重复消费

手动提交的场景:

高可靠性场景必须用手动提交,比如:

  • 订单支付消息:不能丢,不能重复扣款
  • 数据同步消息:精确控制消费进度

最佳实践:

java
// 同步提交:处理完一批,提交一次,可靠性优先
consumer.commitSync();

// 异步提交:吞吐量优先,配合回调处理失败
consumer.commitAsync((offsets, exception) -> {
    if (exception != null) {
        log.error("提交失败", exception);
    }
});

我的建议:

  • 业务消息:手动提交 + 幂等性保障,重复消费也能处理
  • 日志收集、监控数据:自动提交可接受,偶尔丢几条无所谓
  • 最关键的一点:Offset 提交策略要和业务幂等设计配合使用

【面试题】5. 消息队列如何保证消息不丢失?生产端、Broker 端、消费端分别怎么做? ​

【标准答案】 这道题是面试高频题,考查消息可靠性的全链路保障。

生产端保障:

  1. RabbitMQ:

    • 开启 Confirm 模式:channel.confirmSelect()
    • 同步确认:每发一条等 waitForConfirms(),可靠但慢
    • 异步确认:addConfirmListener 批量确认,性能好
  2. Kafka:

    • acks=all(或 acks=-1):Leader 和所有 ISR 副本都确认才算成功
    • 配合 retries 设置重试次数
    • enable.idempotence=true:启用幂等生产者,避免重试导致重复
  3. RocketMQ:

    • 同步发送:send() 返回 SendResult 才算成功
    • 异步发送:提供回调函数,失败有补救
    • 单向发送(one-way):不保证可靠性,适合日志场景

Broker 端保障:

  1. 消息持久化:

    • RabbitMQ:队列和消息都要 durable=true,消息设置 DeliveryMode=2
    • Kafka:log.flush.interval.messages 控制刷盘,默认依赖 PageCache
  2. 副本机制:

    • Kafka:每个分区至少 3 个副本,min.insync.replicas=2,至少 2 个副本写入成功
    • RocketMQ:主从同步复制,同步模式 syncMaster
  3. 硬件层面:

    • RAID 磁盘阵列
    • SSD 提升刷盘性能
    • 不完全依赖 PageCache,关键业务配置同步刷盘

消费端保障:

  1. 手动确认:

    • RabbitMQ:手动 basicAck,处理完再确认
    • Kafka:手动提交 Offset,enable.auto.commit=false
    • RocketMQ:CONSUME_SUCCESS 才算消费完成
  2. 幂等性设计:

    • 消息带唯一 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 为空怎么办?

两种情况:

  1. unclean.leader.election.enable=false(默认):ISR 为空,分区不可用,直到 ISR 中有副本恢复
  2. 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=2
  • unclean.leader.election.enable=false
  • acks=all

这样保证:即使 1 台 Broker 挂了,数据依然有两份,不丢消息。


【面试题】7. 消息队列如何保证消息顺序性?全局有序和分区有序怎么实现? ​

【标准答案】 这道题考查消息顺序性保障,是分布式系统的经典难题。

首先明确:严格的全局有序和高吞吐是矛盾的。

消息队列的顺序性分三个层次:

1. 全局有序: 整个 Topic 的所有消息严格按发送顺序消费。

实现方式:

  • 单分区、单消费者
  • 性能瓶颈明显,吞吐量受限

适用场景:订单状态流转、账户余额变更等强顺序要求。

2. 分区有序: 同一分区内消息有序,不同分区无序。

Kafka 实现:

  • Producer 发送时指定分区键(如订单 ID)
  • 相同订单的消息进入同一分区
  • Consumer 按分区顺序消费

RocketMQ 实现:

  • 发送时指定 MessageQueueSelector
  • 相同业务 ID 的消息进入同一队列

3. 应用层有序: 多个分区,但业务层面保证顺序。

实现方式:

  • 消息带序列号或版本号
  • Consumer 维护每个业务 ID 的最后处理序号
  • 乱序到达的消息缓存等待,超过序号的先缓存

生产环境的坑:

即使配置了分区有序,实际还可能乱序:

  1. Producer 重试: 第一条消息失败重试,第二条成功先发送,顺序颠倒

    • 解决:max.in.flight.requests.per.connection=1,限制并发请求数
  2. Consumer 多线程: 同一分区消息被多线程并发处理

    • 解决:单线程消费,或用内存队列+单线程分发
  3. Consumer Group 重平衡: 分区重新分配,Offset 回退或跳跃

    • 解决:重平衡期间暂停处理,或业务层去重兜底

实际经验:

大部分场景用分区有序就够了:

  • 订单系统:订单 ID 做分区键,同一订单的状态变更消息保证顺序
  • 用户消息流:用户 ID 做分区键,同一用户的推送有序

总结:全局有序牺牲吞吐,分区有序够用,应用层有序最灵活但开发成本高。


【面试题】8. RabbitMQ 如何实现高可用?镜像队列和仲裁队列有什么区别? ​

【标准答案】 这道题考查 RabbitMQ 集群高可用方案。

RabbitMQ 高可用架构:

经典方案是镜像队列(Classic Mirrored Queue),现在官方推荐仲裁队列(Quorum Queue)。

镜像队列:

工作原理:

  • 主队列在 Master 节点
  • 消息同步复制到 Slave 节点
  • Consumer 从 Master 消费
  • Master 挂了,Slave 提升为 Master

配置方式(策略):

bash
rabbitmqctl set_policy ha-all "^order\." \
  '{"ha-mode":"all","ha-sync-mode":"automatic"}'

问题:

  • 同步复制影响性能,写入要等所有 Slave 确认
  • 网络分区时可能出现脑裂,数据不一致
  • 镜像队列已标记为"未来移除"

仲裁队列(Quorum Queue):

RabbitMQ 3.8+ 引入的新方案:

  • 基于 Raft 共识算法
  • 默认需要仲裁多数节点(如 3 节点需 2 节点确认)
  • 天然防脑裂,数据一致性更强

创建方式:

java
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. 消息积压了怎么处理?线上紧急情况怎么快速恢复? ​

【标准答案】 这道题是真实生产场景的高频问题,考查故障处理能力。

消息积压的原因:

  1. Consumer 处理变慢(数据库慢查询、第三方接口超时)
  2. Consumer 挂了,积压在队列
  3. Consumer 扩容了,但分区数不够,无法并行
  4. Producer 突发流量洪峰,Consumer 处理不过来

线上紧急处理四步走:

第一步:快速止血,恢复服务

如果是 Consumer 挂了:

  • 重启 Consumer,先恢复消费
  • 如果是代码 Bug,紧急回滚或热修复

如果是数据库压力太大:

  • Consumer 临时暂停消费,保护下游
  • 先解决数据库瓶颈(扩容、索引优化)

第二步:临时扩容,加速消费

Kafka 场景:

  • 问题:分区数有限,Consumer 数不能超分区数
  • 方案:临时创建新 Topic,分区数扩 10 倍
  • 用临时 Consumer 把积压消息转发到新 Topic
  • 新 Topic 用更多 Consumer 并行消费

RocketMQ 场景:

  • 直接增加 Consumer 实例(不限于分区)
  • 调整消费线程池大小

RabbitMQ 场景:

  • 增加 Prefetch Count,提高单 Consumer 吞吐
  • 临时加 Consumer 实例

第三步:业务降级,保护核心

如果消费速度还是跟不上:

  • 非关键业务消息临时丢弃或延迟处理
  • 核心业务优先处理(如支付消息优先于通知消息)
  • 写入文件或数据库暂存,后续批量回补

第四步:根因分析,防止复发

处理完紧急情况后:

  • Consumer 性能优化(异步处理、批量入库)
  • 数据库慢查询治理
  • 监控告警完善(积压量超阈值立即告警)
  • 演练预案,团队熟悉处理流程

一个真实案例:

某电商大促,订单消息积压 500 万条。

处理过程:

  1. Consumer 挂了 → 重启,但数据库撑不住
  2. 临时方案:Consumer 改为写文件,不动数据库
  3. 积压消息导出,离线批量入库
  4. 入库完成后再消费新消息
  5. 大促后优化数据库索引,下次压测验证

预防措施:

  • 压测演练:模拟积压场景,验证恢复方案
  • 容量规划:Consumer 吞吐量 × 1.5 倍冗余
  • 监控告警:积压量超阈值立即通知
  • 限流降级:Producer 端限流,保护 Consumer

【面试题】10. RabbitMQ、Kafka、RocketMQ 怎么选?各自适合什么场景? ​

【标准答案】 这道题是架构选型必问,考查综合判断能力。

核心对比表格:

维度RabbitMQKafkaRocketMQ
吞吐量万级 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(灵活路由)

我的经验:

问三个问题做决策:

  1. 吞吐量多少?>10 万 QPS → 排除 RabbitMQ
  2. 需要事务消息吗?是 → RocketMQ 或 Kafka
  3. 团队熟悉什么?学习曲线也是成本

没有最好的 MQ,只有最适合的。先明确业务需求,再选技术方案。


本轮题库生成完毕,共 10 道面试题。