消息队列学习计划 - 阶段三:面试高频问题

目标:准备所有 MQ 相关面试问题,覆盖基础到进阶,能应对字节/腾讯等大厂面试
预计周期:1 周,每天 1-2 小时(以记忆和模拟为主)


面试问题分类

类别 问题数量 难度 覆盖公司
基础概念 5 题 字节/腾讯/美团
进阶原理 8 题 ⭐⭐ 字节/腾讯/阿里
项目相关 5 题 ⭐⭐ 字节/腾讯(必问)
系统设计 4 题 ⭐⭐⭐ 腾讯/阿里/字节核心部门
联级追问 不限 ⭐~⭐⭐⭐ 视回答情况延伸

Part 1:基础概念(5 题)

要求:能用自己的话解释清楚,不要求背答案


Q1:为什么需要消息队列?不用会怎样?

回答思路(自己的话版):

不用 MQ 的问题:

  • 系统 A 直接调用 B,两个系统强耦合。A 升级改接口,B 跟着改
  • 秒杀场景 10 万人下单,后端只能处理 1 万,其余直接崩
  • 下单后要发短信/邮件/通知,串行执行用户等半天

用 MQ 的价值:

  • 解耦:A 只管发消息,B/C/D 各接各的,互不影响
  • 削峰:10 万请求先入队列,后端慢慢处理,不崩
  • 异步:下单 500ms → 发MQ → 100ms 返回,短信通知异步处理

追问:如果不用 MQ,还有什么替代方案?

  • 直接 RPC(无法削峰,耦合)
  • 数据库表做队列(轮询消耗资源,不实时)
  • 本地文件 + 异步线程(无法分布式,不可靠)

Q2:Kafka 为什么能做到高吞吐?

回答思路(六个点,每个记住一句话):

  1. 顺序写入:写磁盘永远在末尾追加,顺序 I/O 速度接近内存
  2. 零拷贝:sendfile 系统调用,数据从磁盘到网卡跳过人态,绕过用户缓冲区,减少 CPU 拷贝
  3. 批量处理:消息攒一批再发送/消费,减少网络往返次数
  4. 页缓存(Page Cache):利用 Linux 内存缓存,热数据不需要每次都读磁盘
  5. 稀疏索引.index 文件按固定间隔建索引,不是每条消息都建,减小索引体积
  6. 多分区并行:Partition 之间相互独立,磁盘并行写入

一个综合回答(30 秒版):

Kafka 高吞吐主要靠四点:磁盘顺序写入避免随机 I/O;sendfile 零拷贝减少 CPU 拷贝和数据复制;利用 Linux Page Cache 缓存热数据;消息批量发送和压缩减少网络开销。再加上多 Partition 分区并行处理,吞吐自然就上去了。

追问:零拷贝具体怎么实现的?和传统 I/O 有什么区别?

  • 传统 I/O(4次拷贝):磁盘→内核缓冲区→用户缓冲区→Socket缓冲区→网卡
  • 零拷贝(3次拷贝 + 1次系统调用):磁盘→内核缓冲区(sendfile)→网卡
  • mmap:映射文件到用户空间,跳过一次拷贝,适合读多写少场景

Q3:Kafka 的 ACK 机制有几种?生产环境用哪个?

三种 ACKs:

ACK 含义 可靠性 性能 适用场景
acks=0 发出去就不管了 最低,可能丢消息 最高 日志采集、监控数据(丢了无所谓)
acks=1 等 Leader 写入成功 中等,Leader 挂会丢 中等 一般业务场景(默认配置)
acks=-1/all 等 ISR 所有副本都写入 最高 最低 金融、订单等核心业务

生产环境建议:

  • 一般业务:acks=1,配合重试机制(retries=3)
  • 核心业务:acks=-1,配合 min.insync.replicas=2
  • 纯日志/监控:acks=0,追求最高吞吐

追问:ISR 是什么?

ISR(In-Sync Replicas)是当前和 Leader 保持同步的副本集合。只有在 ISR 里的副本,才算"活着的",Leader 选举只从 ISR 里选。


Q4:消息丢失了怎么办?从哪几个环节入手?

三个丢消息的环节:

生产者 → Kafka Broker → 消费者
  环节1       环节2        环节3

环节1:生产者发送失败

  • 原因:网络抖动、Broker 无响应
  • 解决:acks=-1 + retries=3 + 异步回调检查发送结果
  • 兜底:本地持久化,失败后补偿重发

环节2:Broker 持久化失败

  • 原因:Follower 未同步完成时 Leader 挂了
  • 解决:acks=-1 + replication.factor>=3 + min.insync.replicas=2

环节3:消费者处理时崩溃

  • 原因:处理完还没提交 offset 就崩了
  • 解决:手动提交 offset(enable.auto.commit=false),处理完业务逻辑再提交
  • 或者:业务处理和 offset 提交放在同一个事务里

一个综合回答:

消息丢失有三个环节,每个环节都要处理。生产者端用 acks=-1 和重试;Broker 端配置多副本和 ISR;消费者端手动提交 offset,确保业务处理完再提交。核心思路是在架构上做冗余,在代码上做幂等。


Q5:如何保证消息不重复消费(幂等性)?

什么时候会重复消费:

  • 生产者超时重试,同一条消息发了两遍
  • 消费者处理完、提交 offset 前崩溃,重启后重复消费

解决方案(三层防线):

1. 生产者幂等(源头控制)

  • 开启幂等性:enable.idempotence=true
  • Kafka 自动给每条消息加唯一 producer.id + sequenceNumber
  • Broker 会丢弃重复序列号的消息

2. 消费者幂等(消费端兜底)

  • 数据库唯一索引:消费前先查库,有记录说明处理过,跳过
  • Redis 去重:消费前先 SET NX key,过期时间设长一点
  • 业务去重:消息中带唯一 ID,消费逻辑判断"是否处理过"

一个综合回答:

幂等性要分两层做。生产者端开启 Kafka 自带的幂等性(enable.idempotence=true),从源头防止重复发送。消费端做业务层去重,用 Redis 的 SETNX 或者 MySQL 的唯一索引来保证每条消息只处理一次。两者结合,基本不会出现重复消费问题。


Part 2:进阶原理(8 题)


Q6:消费者 Rebalance 是什么?什么时候触发?如何优化?

Rebalance 定义:

Consumer Group 中 Consumer 数量变化时(加入/离开/崩溃),重新分配 Partition 的过程。

触发条件:

  • Consumer 重启
  • Consumer 处理超时未发心跳(session.timeout.ms 触发)
  • Consumer 主动离开(调用 unsubscribe)
  • Consumer 数量超过 Partition 数(多余的 Consumer 空闲)

Rebalance 过程(三个阶段):

  1. JOIN_GROUP:所有 Consumer 加入组,选举出 Group Leader
  2. SYNC_GROUP:Group Leader 制定分区分配方案,同步给所有 Consumer
  3. HEARTBEAT:正常消费,定期心跳维持组成员关系

Rebalance 风暴问题:

  • 大量 Consumer 同时 join/leave,导致每次 Rebalance 期间所有 Consumer 停止消费
  • 服务短暂不可用

优化方案:

  1. session.timeout.ms 设置合理(不要太短,建议 10s 以上)
  2. heartbeat.interval.ms 设为 session.timeout 的 1/3
  3. 减少 Consumer 频繁重启
  4. Consumer 处理逻辑要快,避免心跳超时

追问:Partition 数和 Consumer 数的关系?

Partition 数决定最大并发消费数。Consumer 数量超过 Partition 数后,多余的 Consumer 空闲。所以 Partition 数要提前规划好,不要太少也不要太多。


Q7:Kafka 的 Topic 删除是立即删除吗?日志何时清理?

两种清理策略:

1. delete 策略(默认):

  • 超过 retention.msretention.bytes 阈值后标记删除
  • 不是立即删除,由后台线程定时清理

2. compact 策略:

  • 同一 key 的消息只保留最新一条(用于状态存储,如数据库变更日志)
  • 保证每个 key 至少有一条有效消息

清理时机:

  • 日志段(Log Segment)合并时才真正删除旧文件
  • 不是按时间精确清理,有一定延迟

Q8:Kafka 的 Leader 选举过程?Controller 是什么?

Leader 选举(分区级):

  • 当 Leader 所在的 Broker 挂了
  • 从 ISR 列表中选择一个 Follower 成为新 Leader
  • 如果 ISR 为空?取决于 unclean.leader.election.enable
    • false(默认):不允许从非 ISR 选举,牺牲可用性保证一致性
    • true:允许从非 ISR 选举,牺牲一致性保证可用性

Controller(控制器):

  • 集群中只有一个 Controller(通过 ZooKeeper 选举)
  • Controller 职责:
    • 管理分区 Leader 选举
    • 感知 Broker 上下线
    • 管理 Topic 创建/删除
  • Controller 挂了怎么办?ZooKeeper 重新选举,新 Controller 立即接管

追问:选举过程中服务会中断多久?

Leader 切换通常在毫秒级完成。但切换期间发往该 Partition 的请求会短暂失败(新版 Java Client 会自动重试)。实际用户感知到的中断通常在 100ms 以内。


Q9:Kafka vs RabbitMQ 的区别?如何选型?

对比:

维度 Kafka RabbitMQ
吞吐量 极高(百万级 QPS) 中等(万级 QPS)
延迟 低(毫秒级) 较低(毫秒到秒级)
消息顺序 单 Partition 内有序 队列内有序
消息可靠性 可配置(ACK机制) 支持 Publisher Confirm
消费模式 Pub/Sub(消费组) P2P + Pub/Sub
功能丰富度 简单(日志型) 丰富(交换机/路由/死信)
多租户 支持 支持
适用场景 日志采集、实时流处理 业务消息、任务队列、复杂路由

选型建议:

  • 选 Kafka:高吞吐场景、日志收集、流处理、需要分区并行
  • 选 RabbitMQ:业务消息、复杂路由(多交换机)、需要丰富的消息特性(延迟队列、优先级队列)
  • 两者也可以同时用:Kafka 做日志/事件流,RabbitMQ 做业务消息

Q10:Kafka 如何实现消息顺序性?

三个层次的顺序保证:

1. Kafka 单 Partition 内有序(Partition 级):

  • 同一 Partition 内的消息按 offset 顺序消费
  • 保证同一条生产者发送的多条消息按顺序到达消费者

2. 全局顺序(单 Partition + 单 Consumer):

  • Topic 只有 1 个 Partition
  • 只有 1 个 Consumer
  • 代价:失去了并行处理能力

3. 业务层面的局部有序(推荐):

  • 按业务 key(如 user_id)哈希到同一 Partition
  • 保证同一用户的操作有序
  • 不同用户的操作可并行处理

一个回答:

Kafka 单个 Partition 内是有序的。如果需要全局有序,可以设置单 Partition,但会失去并行能力。更常见的做法是按业务 key(比如用户 ID)路由到同一个 Partition,保证同一用户的操作有序,不同用户之间并行处理。这是业务和性能的平衡。


Q11:Kafka 消息积压了怎么办?

原因分析:

  • Consumer 消费速度 < Producer 发送速度(Consumer 处理太慢)
  • Consumer 挂了(没有消费)
  • Partition 数量太少(并发度不够)

解决方案:

短期应急:

  1. 增加 Consumer 实例(消费端水平扩展)
  2. 增加 Consumer 线程数(单实例内并行)
  3. 增加 Partition 数(配合增加 Consumer)

长期优化:

  1. 优化 Consumer 处理逻辑(减少数据库查询、加缓存、批量处理)
  2. 消费者端加监控,提前发现积压趋势
  3. 生产端限流,避免突发流量

追问:Partition 数量能调吗?

可以增加(kafka-topics --alter),但不能减少。减少 Partition 需要删除整个 Topic 后重建。


Q12:如何设计一个消息延迟投递系统?

方案一:时间轮(Redis ZSet / Kafka 单 Partition 按时间戳排序)

把延迟消息写到 Redis ZSet,score = 执行时间戳
后台线程轮询 ZSet,score <= 当前时间戳 的消息取出来发送

方案二:Kafka + 延迟消费者(推荐在你项目里用这个)

延迟消息发到 DelayTopic(TTL=延迟时间)
TTL 过期后消息落入真实 Topic
Consumer 从真实 Topic 消费

方案三:RabbitMQ 延迟插件

  • 消息设置 x-delay 头,指定延迟时间
  • 延迟到期后自动进入队列

方案四:多级时间轮(你的内存池项目就是时间轮,同一个思路)

  • 用你写过的时间轮数据结构实现延迟队列
  • 可以作为面试亮点:你不仅用过时间轮,还自己实现过

追问:Redis ZSet 做延迟队列有什么缺点?

单个 Redis 实例有性能瓶颈;不支持消息持久化(Redis 挂了消息丢失);多 Consumer 时 ZSet 的 pop 操作需要加锁。


Q13:如果 Kafka 集群整体挂了怎么办?

这是一个灾难恢复问题,考察架构设计能力。

预防措施:

  • 多 Broker 部署 + 多副本(replication.factor >= 3
  • 跨机架/跨 AZ 部署(不同机房)
  • acks=-1 + min.insync.replicas=2
  • 定期检查 ISR 健康状态

恢复步骤:

  1. 检查 ZooKeeper / KRaft 状态,确认 Controller 存活
  2. 定位哪些 Broker 挂了,尝试重启恢复
  3. 如果 Broker 无法恢复,检查哪些 Partition 的 Leader 不可用
  4. 手动触发 Leader 选举(如果配置允许 unclean.leader.election
  5. 消费者从上次提交的 offset 继续消费,可能有小部分消息需要补偿处理

降级方案:

  • MQ 不可用时,Producer 端本地缓存消息
  • 服务降级:关闭非核心功能,优先保证核心链路
  • 告警:Kafka 挂了应该有监控告警通知到人

Part 3:项目相关(5 题)

基于你在阶段三完成的项目,提前准备好回答


Q14:你们项目的 Kafka 吞吐量是多少?怎么测的?

回答模板:

我用 Kafka 自带的 kafka-producer-perf-test 工具测的。单 Producer 异步发送,record 大小 1KB,acks=1,吞吐量达到 5万条/秒。Consumer 端单线程消费,处理能力约 3万条/秒。主要瓶颈在 MySQL 批量写入,Consumer 加了本地缓冲后提升到 5万条/秒

追问:瓶颈在 MySQL 的话怎么优化?

  • 批量写入(攒 100 条再 INSERT)
  • 读写分离(写 MySQL 主库,读从库)
  • 分库分表(按时间或业务维度分)
  • 换成 TiDB 或 ClickHouse

Q15:如果你的 Kafka Consumer 崩溃了,消息会丢失吗?

结合你的项目设计回答:

我在 Consumer 端做了两层保护。第一,手动提交 offset,业务处理成功后才提交。第二,用 Redis 记录已处理消息的 ID,消费前先查重。这两层保证了即使 Consumer 崩溃,重启后要么从上次 offset 继续(可能重复消费),要么跳过已处理的消息(幂等消费)。不会丢消息。

追问:手动提交 offset 会不会更慢?

不会。我用的是异步提交(commitAsync),提交 offset 和处理下一批消息是并行的,不阻塞消费。只有在 Consumer 退出时才用同步提交(commitSync)确保最后一批消息的 offset 被正确提交。


Q16:你的项目为什么选 Kafka 而不是 RabbitMQ?

结合你的技术栈和场景回答:

主要有两个原因。第一,我项目需要高吞吐,日志数据量很大(每秒几千条),Kafka 的吞吐远高于 RabbitMQ,更适合这个场景。第二,我的项目是日志分析,需要按时间范围查询消息,Kafka 的 Log Segment 结构和稀疏索引更适合。而 RabbitMQ 更适合业务消息的复杂路由,我的场景用不到。


Q17:Kafka 和你已有的 Reactor 框架有什么区别和联系?

这是 C++ 后端专属问题,展示你的技术融合能力:

本质上,两者的设计思想是相通的。Kafka Broker 的网络层是 Java NIO 实现的 Reactor 模式——一个 Selector 监听多个连接 Channel,有事件就分发给 Handler 处理。我的 C++ Reactor 框架也是这个思路,只是实现语言不同。

区别在于:Kafka 的 Reactor 是单 Reactor + 多线程 Handler(一个线程处理多个连接),而我的框架是多 Reactor(主从 Reactor 线程池)。Kafka 为了保持高吞吐做了批量处理和零拷贝优化,我在网络框架里也可以借鉴 Kafka 的批量发送思路做进一步优化。


Q18:你的消息队列项目遇到过什么困难?怎么解决的?

提前准备好 1-2 个真实遇到的困难:

困难一:Consumer 消费速度跟不上

现象:压测时消息开始积压,消费延迟越来越高。
分析:用 kafka-consumer-groups --describe 发现 Consumer CPU 使用率 100%,瓶颈在业务处理逻辑太慢(每次消费都要查 MySQL)。
解决:加了 Redis 本地缓存热点数据,MySQL 查询减少 80%;同时增加 Consumer 线程数,吞吐量翻倍。

困难二:Rebalance 时服务短暂不可用

现象:Consumer 重启时会有一小段时间所有分区重新分配,这期间服务中断。
分析:Rebalance 触发期间所有 Consumer 停止消费,新 Consumer 加入时旧 Consumer 需要放弃已有分区。
解决:尽量避免频繁重启 Consumer;调整 session.timeout.ms 到 30 秒,减少误触发;将非核心功能拆出去用独立 Consumer。


Part 4:系统设计(4 题)


Q19:如何设计一个每天 10 亿消息的日志收集系统?

架构设计(分层):

日志源(App Server)
    ↓(fluentd/filebeat)
Kafka 集群(100+ Broker,1000+ Partition)
    ↓
消费者集群(100+ Consumer)
    ↓
存储层(HDFS / ES / ClickHouse)
    ↓
查询 / 分析平台(Grafana / Kibana)

关键设计点:

  1. 分区策略:按服务名 + 日期分区,方便按时间范围查询
  2. 副本配置replication.factor=3min.insync.replicas=2
  3. 数据保留:7 天热数据存 Kafka,超过的进入冷存储(HDFS)
  4. 消费者分组:多个 Consumer Group 分别做统计/存储/告警
  5. 容错:Consumer 挂了自动 Rebalance,数据不丢

Kafka 集群规划:

  • 消息大小 1KB,每天 10 亿条 → 约 10TB/天
  • 副本 3 份 → 30TB/天 → 7 天约 210TB
  • Broker 配置:每台 48 核 128G,12 × 4TB SSD

Q20:如何设计一个秒杀系统中的消息队列方案?

场景:100 万人抢购 1000 件商品

不用 MQ 的问题:

  • 10 万人同时下单,数据库直接被打爆
  • 超卖:并发更新库存导致数据不一致

用 MQ 解决的架构:

用户下单请求
    ↓
限流(Nginx / API Gateway 限制 QPS)
    ↓
写入 MQ 订单队列(削峰)
    ↓
多个 Worker 异步处理
    ├→ 库存服务(原子扣减)
    ├→ 订单服务(创建订单)
    └→ 通知服务(发送通知)
    ↓
返回用户"下单成功,正在处理"

MQ 关键设计:

  1. 顺序性:同一商品的秒杀要保证顺序(按商品ID路由到同一 Partition)
  2. 幂等性:重复下单要能识别并拒绝(Redis SETNX 防重复)
  3. 库存一致性:用 Redis 预扣减库存 + MQ 异步落库,双重保障
  4. 超时处理:下单超时(如 10 分钟未支付),消息不处理,库存自动释放

Q21:如何保证 MQ 的高可用?

从四个层面回答:

1. MQ 集群层面:

  • Broker 多副本(replication.factor >= 3
  • 跨机房部署(不同可用区)
  • ISR 机制保证数据不丢

2. 生产者层面:

  • acks=-1 + 重试机制
  • 集群部署(多个 Broker),一个挂了自动切换

3. 消费者层面:

  • Consumer Group 多实例(一个挂了其他接管)
  • 手动提交 offset
  • 幂等消费

4. 监控告警:

  • 消息积压告警(队列深度超过阈值)
  • Broker 存活告警
  • 消费延迟告警

Q22:如果让你用 C++ 从零实现一个简化版 Kafka,你会怎么做?

这是一个综合设计题,考察你对 Kafka 核心原理的理解深度。

核心模块设计:

1. 网络层(借鉴 Reactor 模式):

Acceptor(接收连接)→ 分配给 EventLoop → Channel 监听读写事件
EventLoop 使用 epoll(你已经实现过)

2. 消息存储:

Topic → Partition → Log File(顺序追加)
        → Index File(稀疏索引)
        → Segment File(按大小滚动)

3. 分区策略:

Producer 发送时 → 根据 key 哈希选择 Partition
→ 写入对应 Partition 的 Leader
→ Leader 异步同步给 Follower(类 Raft 共识)

4. 消费模型:

Consumer Group → 每个 Consumer 分配若干 Partition
→ 维护消费进度(offset)
→ 支持重平衡(Rebalance)

关键区别(你不需要实现的部分):

  • Controller 选举(可用 ZooKeeper 或简化版)
  • 多副本强一致性(简化版用单副本)
  • 事务消息(复杂,面试时提一下即可)

一个综合回答:

我会分四层做。网络层用我已有的 Reactor 框架实现,一个 Acceptor 接收连接,多个 EventLoop 处理 I/O 事件。存储层参考 Kafka 的 Log Segment 结构,按时间或大小滚动切分文件,配套稀疏索引支持快速查找。分区路由层按 key 哈希决定写入哪个 Partition,Leader 接收写入后异步同步给 Follower。消费层维护 Consumer Group 和 offset,支持 Rebalance。这个简化版不支持强一致性多副本,如果要加,可以用 Raft 协议实现 Leader 选举和日志复制(etcd/Consul 就是这么做的)。


Part 5:面试模拟(自测)

模拟面试流程(每题 3 分钟)

找一个同学或对着镜子,按以下流程回答每道题:

  1. 结论先行(5 秒):先说核心答案
  2. 分点展开(2 分钟):2-3 个要点,每个要点一句话解释
  3. 举例说明(30 秒):用一个具体场景或数字佐证
  4. 追问预判(自问自答):如果面试官追问,你会怎么答

自测检查清单

  • Q1-Q5 基础概念,能在 1 分钟内回答清楚
  • Q6-Q13 进阶原理,能画图解释
  • Q14-Q18 项目问题,能结合自己项目回答
  • Q19-Q22 系统设计,能给出完整架构
  • Q22 C++ 实现题,能讲清楚设计思路

面试核心技巧

1. 用数字说话

  • 不要说"很快",说"QPS 5 万,p99 延迟 10ms"
  • 不要说"支持高并发",说"实测 5000 并发连接稳定运行"

2. 画图比说更清晰

  • Kafka 架构图、Rebalance 流程图、存储结构图
  • 面试时在白板上画,比纯语言描述更直观

3. 展示主动思考

  • 说完方案后主动说"不过这个方案也有缺点,比如…"
  • 展示你不是只会用,还知道边界和 trade-off

4. 关联已有项目

  • 每个知识点尽量关联到你做过的项目
  • 比如"我在内存池里用过类似的思想…" “我的网络框架和 Kafka 的网络层设计思路一致…”

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐