Administrator 9 阅读 面试

消息队列实战面试题

消息队列实战面试题(Redis / Kafka / RabbitMQ 等)

面向 Java 后端,强调原理 + 项目落地经验


1. 什么是消息队列?为什么要用消息队列?

口述版:

消息队列是一种异步通信中间件,生产者把消息发送到队列,消费者从队列中取消息处理,实现系统解耦。

主要解决三个问题:

  1. 异步处理:提高响应速度。比如用户下单后,发短信、扣积分、写日志等非核心操作放到队列异步执行,接口快速返回。
  2. 系统解耦:上游不需要知道下游有谁,新增消费者不需要改生产者代码。
  3. 削峰填谷:应对流量突发。秒杀、活动时大量请求先进队列,后端按自己节奏消费,保护数据库。

其他好处还有:最终一致性、广播通知、日志收集等。


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 全局有序。

实现顺序的常用做法:

  1. 单 Partition:简单但失去并行能力,吞吐低。
  2. 按业务 Key 分区:发送时指定相同的 key(如 orderId、userId),相同 key 的消息进入同一个 Partition,从而保证局部有序。
  3. 消费端单线程消费某个 Partition,或在业务上做顺序控制。

实际项目中,大多数业务只需要「局部有序」(比如同一个订单的状态变更有序),而不是全局有序。


6. 如何处理消息重复消费?(幂等性)

口述版:

消息队列通常是 至少一次(at-least-once) 语义,所以重复消费几乎不可避免,必须在业务层做幂等。

常见手段:

  1. 唯一业务 ID + 数据库唯一索引:插入前检查或靠唯一约束防重。
  2. Redis 去重:用消息唯一 ID 做 setnx,处理过就跳过。
  3. 状态机:根据业务状态判断是否已处理(如订单已支付就不再处理支付消息)。
  4. 数据库乐观锁 / 版本号。
  5. Kafka 幂等生产者 + 事务(正好一次语义),但仍建议业务层再做一层幂等。

核心原则: 不要依赖 MQ 保证不重复,而是假设一定会出现重复,把消费逻辑设计成幂等的。


7. 消费者提交 Offset 的时机?自动提交有什么问题?

口述版:

  • 自动提交(enable.auto.commit=true):按时间间隔自动提交。问题是:可能业务还没处理完就提交了 Offset,导致消息丢失;或者处理完但还没提交就挂了,导致重复消费。
  • 手动提交(推荐):
    • 业务处理成功后再调用 commitSync() 或 commitAsync()。
    • 同步提交更可靠,异步性能更好,可结合使用。

最佳实践:关闭自动提交,处理完业务再手动提交,并配合幂等设计。


8. 什么是死信队列?怎么处理?

口述版:

死信队列(Dead Letter Queue, DLQ)用来存放无法被正常消费的消息(重试多次仍失败、格式错误、业务拒绝等)。

作用:

  • 防止坏消息一直阻塞消费。
  • 方便后续排查和人工处理。

处理流程通常是:

  1. 消费失败后重试(可设置重试次数和间隔)。
  2. 超过重试次数后,把消息转发到死信队列。
  3. 监控死信队列,告警并人工介入(修复数据后重新投递,或记录后丢弃)。

Kafka 本身没有内置 DLQ,需要业务或中间件(如 Spring Kafka、自研)实现;RabbitMQ、RocketMQ 对死信支持更完善。


9. Redis 可以做消息队列吗?怎么实现?有什么局限性?

口述版:

可以做轻量级消息队列,常见三种方式:

  1. List:LPUSH + BRPOP(阻塞弹出),实现简单队列。
  2. Pub/Sub:发布订阅,实时性好,但消息不持久化,消费者不在线会丢消息。
  3. Stream(Redis 5.0+):支持消费者组、ACK、持久化,更接近专业 MQ。

局限性(重要):

  • 消息可靠性不如专业 MQ(持久化、副本机制弱于 Kafka)。
  • 不适合超大数据量堆积。
  • 功能单一(事务消息、延迟消息、死信等需要自己实现)。
  • 内存成本较高。

适用场景: 实时性要求高、消息量不大、允许少量丢失的场景(如实时通知、简单异步任务)。核心业务消息建议用 Kafka / RocketMQ。


10. 如何实现延迟消息 / 延时队列?

口述版:

不同 MQ 实现方式不同:

  • RocketMQ:原生支持延迟消息(固定延迟级别或任意时间)。
  • RabbitMQ:通过死信队列 + TTL 实现,或使用延迟插件。
  • Kafka:本身不支持,常见做法:
    1. 时间轮 + 本地延迟(业务自己实现)。
    2. 使用 Redis ZSet 存到期时间,定时扫描投递到 Kafka。
    3. 使用专门的延迟队列中间件。
  • 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. 消息积压怎么处理?

口述版:

消息积压是线上常见问题,排查和处理思路:

  1. 先看原因:

    • 消费者处理太慢(业务逻辑重、DB 慢、下游接口慢)
    • 消费者挂了或数量不够
    • 生产者突发流量
    • 分区数太少,并行度不够
  2. 紧急处理:

    • 扩容消费者实例(水平扩展)。
    • 临时增加分区(注意:分区只能增不能减,且对有序性有影响)。
    • 优化消费逻辑(批量处理、异步化、减少 DB 交互)。
    • 降级非核心消费逻辑。
  3. 长期方案:

    • 监控 Consumer Lag(积压量)。
    • 提前做好容量规划。
    • 对高峰流量做限流或削峰。

Kafka 常用 kafka-consumer-groups.sh 或监控系统看 Lag。


13. 事务消息怎么实现?(以 RocketMQ / Kafka 为例)

口述版:

事务消息解决的是「本地事务和发消息」的原子性问题(比如扣库存和发订单消息要么都成功,要么都失败)。

RocketMQ 半消息机制(最经典):

  1. 发送半消息(对消费者不可见)。
  2. 执行本地事务。
  3. 本地事务成功 → 提交消息;失败 → 回滚消息。
  4. 如果确认丢失,Broker 会回查本地事务状态。

Kafka 事务:

  • 使用幂等生产者 + 事务 API(transactional.id)。
  • 可以做到「写入多个 Partition + 提交 Offset」的原子性,常用于 Exactly-Once 语义。

实际业务中,很多团队用「本地消息表 + 定时任务」实现最终一致性,简单可靠。


14. 项目中怎么保证消息的可靠性(端到端)?

口述版(实战回答模板):

我们从三个环节保证:

  1. 生产端:

    • 关键消息同步发送 + 失败重试。
    • 记录发送日志或使用本地消息表。
    • Kafka 设置 acks=all + 幂等。
  2. Broker:

    • 多副本 + ISR 机制。
    • 监控副本同步状态。
  3. 消费端:

    • 手动提交 Offset。
    • 业务幂等(唯一键 / Redis / 状态机)。
    • 失败重试 + 死信队列。
    • 监控消费延迟和死信数量。

整体采用「至少一次 + 业务幂等」策略,而不是强依赖 MQ 的正好一次。


15. Redis Stream 和 Kafka 有什么区别?什么时候用 Redis Stream?

口述版:

对比点 Redis Stream Kafka
定位 轻量流式数据结构 分布式高吞吐消息系统
持久化 RDB/AOF,可靠性一般 强持久化 + 多副本
吞吐量 中等 非常高
消费者组 支持 成熟支持
运维复杂度 低(跟着 Redis) 较高
适用规模 中小规模、实时性要求高 大规模、高可靠、日志场景

用 Redis Stream 的场景:

  • 已经在用 Redis,不想引入新中间件。
  • 消息量不大,对可靠性要求不是极端严格。
  • 需要低延迟的实时流处理。

核心业务、大数据量、强可靠场景,优先选 Kafka 或 RocketMQ。


面试加分点

  1. 能结合自己项目讲:用了什么 MQ、为什么选它、怎么保证不丢不重、有没有遇到过积压或 Rebalance 问题。
  2. 清楚「至少一次 + 幂等」是主流可靠方案。
  3. 能说清 Kafka 分区有序、Offset 提交时机、死信处理。
  4. 知道 Redis 做队列的局限,不把核心业务消息只放在 Redis。
  5. 对事务消息、延迟消息有落地思路。

快速记忆框架:

消息队列面试核心:

为什么用 → 选型对比 → Kafka 架构与可靠性 → 顺序性 → 重复消费(幂等) → Offset 提交 → 死信与积压 → 延迟消息 → Redis 做队列的取舍

把「不丢、不重、有序、积压处理」这四块讲清楚,再结合项目经验,基本就能覆盖大部分实战面试。