消息中间件面试题库
生成时间:2026-05-21 23:33
【面试题】1. Kafka的ISR机制是什么?为什么需要ISR而不是简单地用所有副本同步?
【标准答案】ISR是In-Sync Replicas,即同步副本集合,是Leader维护的一个动态副本列表,只有ISR中的副本才有资格被选为新Leader。
为什么不能简单用所有副本?举个场景:某个Follower因为网络问题或磁盘IO慢,落后Leader太多。如果允许它参与选举,它可能丢失大量消息,导致数据不一致。ISR的作用就是划一条线——只有跟上进度的副本才有资格接班。
关键细节:
replica.lag.time.max.ms参数控制,默认30秒,超过这个时间没拉取消息就会被踢出ISR- 被踢出后继续追,追上了会重新加入
- 这是一种动态淘汰机制,在可用性和一致性之间找平衡
面试加分点:可以提一下unclean.leader.election.enable参数,默认false,禁止非ISR副本成为Leader,保证数据不丢失;如果设为true,牺牲一致性换可用性。
【面试题】2. RabbitMQ的死信队列DLX是什么?哪些情况会产生死信?实际怎么用?
【标准答案】死信队列DLX(Dead Letter Exchange)是个兜底机制,消息变成"死信"后不会被直接丢弃,而是转发到指定的Exchange,统一处理。
三种产生死信的情况:
- 消息被NACK或Reject,且requeue=false
- 消息TTL过期
- 队列满了,新消息进不来
实际应用: 最常见的组合是TTL+DLX实现延迟队列——消息进入普通队列设TTL,过期后变成死信转发到DLX,消费者绑定DLX消费,就实现了延迟投递。比如订单30分钟未支付自动取消。
另一种场景是异常消息集中处理:消费失败的消息进入死信队列,人工排查或定时重试,避免阻塞正常消费流程。
配置方式:队列声明时指定x-dead-letter-exchange和x-dead-letter-routing-key参数即可。
【面试题】3. RocketMQ的事务消息原理是什么?和本地消息表方案相比有什么优劣?
【标准答案】RocketMQ事务消息采用半消息+两阶段提交+回查机制:
第一阶段:生产者发一条半消息到Broker,这条消息对消费者不可见,只是暂存。 第二阶段:生产者执行本地事务,根据结果决定Commit或Rollback。Commit后消息才对消费者可见。
关键设计——回查机制:如果Broker没收到第二阶段确认(比如生产者挂了),会主动回调生产者的接口查询本地事务状态,确保最终一致性。
对比本地消息表方案:
| 维度 | 事务消息 | 本地消息表 |
|---|---|---|
| 实现复杂度 | 较低,MQ内置 | 较高,需自己实现 |
| 性能 | 更高,无额外DB写入 | 每条消息多一次DB操作 |
| 依赖 | 强依赖RocketMQ | 只依赖DB,更通用 |
| 灵活性 | 只适用于RocketMQ | 可用于任何MQ |
简单说:用RocketMQ就选事务消息,更简洁高效;用其他MQ或不想依赖特定MQ特性,就用本地消息表,更通用但开发成本高。
【面试题】4. 从生产者到消费者,消息中间件防止消息丢失的完整链路是什么?
【标准答案】消息丢失可能发生在三个阶段,每个阶段都要有对应措施:
生产端:
- RabbitMQ:开启Confirm模式,异步回调确认
- Kafka:设置
acks=all,Leader和所有ISR副本都确认 - RocketMQ:同步发送+Broker返回ACK,或使用事务消息
存储端(Broker):
- 开启消息持久化:RabbitMQ队列和消息都设durable,Kafka设置
flush策略 - 多副本机制:RabbitMQ镜像队列/仲裁队列,Kafka多副本,RocketMQ主从同步
- 同步刷盘策略(性能换可靠性)
消费端:
- 关闭自动提交,业务处理成功后手动ACK
- 异常时NACK并设置requeue=false,进入死信队列处理
- 关键业务可以做消费确认表的幂等校验
一句话总结:生产要确认、存储要持久+多副本、消费要手动ACK。三个环节缺一不可。
【面试题】5. Kafka为什么快?零拷贝和顺序写在其中扮演什么角色?
【标准答案】Kafka高性能核心三点:顺序写、零拷贝、批量处理。
顺序写:磁盘顺序写比随机写快几个数量级。Kafka把消息追加到CommitLog末尾,不做随机更新,充分利用磁盘带宽。实测顺序写可达600MB/s,随机写只有100KB/s左右。
零拷贝:传统数据传输是 磁盘→内核缓冲区→用户缓冲区→内核socket缓冲区→网卡,四次拷贝。Kafka用sendfile系统调用,数据直接从内核的PageCache到网卡,省去用户态拷贝,只有两次:磁盘→PageCache→网卡。
批量处理:生产者批量发送、压缩传输、消费者批量拉取,减少网络IO次数和系统调用开销。
面试加分点:可以提mmap用于Consumer读取(通过PageCache),但Producer写入用普通FileChannel,因为mmap在写入大文件时有内存映射开销。以及Kafka重度依赖PageCache,生产消费共享缓存,热数据命中率极高。
【面试题】6. RabbitMQ和Kafka选型时怎么决策?各适合什么场景?
【标准答案】先看核心差异:
| 维度 | RabbitMQ | Kafka |
|---|---|---|
| 吞吐量 | 万级/秒 | 十万~百万级/秒 |
| 延迟 | 微秒级 | 毫秒级 |
| 消息模型 | 队列+Exchange路由 | Topic+Partition日志 |
| 消费模式 | 推模式 | 拉模式 |
| 消息回溯 | 不支持 | 支持Offset重置 |
| 功能丰富度 | 延迟队列、死信、优先级等 | 流处理、KSQL、Connect |
选RabbitMQ的场景:
- 消息量不大(万级以内),但需要复杂路由(按routing key分发)
- 需要延迟队列、优先级队列等高级功能
- 对延迟敏感,要求毫秒内响应
- 传统企业应用、订单系统、任务调度
选Kafka的场景:
- 大数据量、高吞吐(日志收集、用户行为、监控数据)
- 需要消息回溯,重新消费历史数据
- 流式处理、实时数仓场景
- 用户行为分析、推荐系统数据管道
简单决策口诀:小流量复杂路由选RabbitMQ,大流量流处理选Kafka。不确定就用Kafka,扩展性更好。
【面试题】7. Kafka Consumer Group的重平衡Rebalance是什么?触发条件和避免策略?
【标准答案】Rebalance是指消费者组内的消费者重新分配分区,分区的Owner从一个消费者切换到另一个。
触发条件:
- 消费者组内消费者数量变化(新加入或挂掉)
- Topic分区数变化
- 消费者主动取消订阅
问题:Rebalance期间整个消费者组会暂停消费,所有分区重新分配,这个过程叫"Stop-the-World"。如果频繁Rebalance,消费吞吐量会严重下降。
避免策略:
- 合理设置
session.timeout.ms和heartbeat.interval.ms,避免误判消费者掉线 - 设置
max.poll.interval.ms比业务处理时间更长,避免处理慢触发Rebalance - 使用静态成员(
group.instance.id),消费者重启时不触发Rebalance - 减少消费者数量变化频率,稳定部署
面试加分点:可以提一下增量Cooperative Rebalance(Kafka 2.4+),只迁移需要重新分配的分区,不像之前要把所有分区重新洗牌,大大减少了"Stop-the-World"时间。
【面试题】8. 如何保证消息的顺序性?分区消息和全局有序分别怎么实现?
【标准答案】消息有序性分两种级别:分区有序和全局有序。
分区有序实现(Kafka):
- 同一分区内消息天然有序,因为追加写入
- 生产者发送时指定分区键,比如订单ID,同一订单的消息发到同一分区
- 消费者单线程消费每个分区,或用内存队列+多worker处理
关键:partitioner按key hash分区,相同key必到同一分区。
全局有序实现:
- Topic只设1个分区——性能差,不推荐
- 更好的方案是接受分区有序,业务层面用版本号或时间戳保证逻辑顺序
RocketMQ顺序消息:
- 全局有序:单Queue,性能差
- 分区有序:同一ShardingKey的消息发到同一Queue,类似Kafka
RabbitMQ有序问题: 天然不保证有序,因为多消费者并行消费。要有序只能单消费者,或业务层用序号重排。
实战建议:大多数场景分区有序足够,比如订单状态变更,同一订单ID的消息进入同一分区,保证该订单的操作有序即可。全局有序是伪需求,通常可以换个设计思路。
【面试题】9. 消息积压了怎么处理?有没有成熟的排查和应急方案?
【标准答案】消息积压是线上常见故障,处理思路分三步:定位原因→应急处理→根治优化。
定位原因:
- 消费者慢了?看消费者日志、CPU、内存、GC情况
- 消费者挂了?检查进程状态、错误日志
- 生产太快?看生产端监控,是否流量突增 -下游服务瓶颈?比如数据库连接池满、第三方接口超时
应急处理:
- 临时扩容消费者:增加消费者实例,快速消化积压
- 降级非核心逻辑:跳过复杂处理,直接落库或转发
- 丢弃过期消息:如果业务允许,跳过N小时前的消息
- 转发到新Topic:用临时消费者快速转发,另起服务慢慢消费
根治优化:
- 提升消费并发度:增加分区数、消费者线程数
- 批量消费:Kafka consumer设置
max.poll.records - 异步处理:消费者只负责快速拉取,丢到线程池处理
- 下游异步化:写数据库改批量插入,调用接口改异步
预防监控:
- 设置积压告警阈值,比如超过10万条消息告警
- 监控消费Lag(Kafka的
consumer-lag指标) - 定期压测消费者吞吐能力
【面试题】10. 如果让你设计一个百万级TPS的消息中间件架构,核心设计点有哪些?
【标准答案】百万TPS不是靠单点优化,而是架构层面的分层设计:
1. 分区横向扩展
- Topic拆分多个Partition,分散到不同Broker
- Partition数=预期吞吐/单分区能力,单分区一般万级TPS
- 自动负载均衡,热点分区自动迁移
2. 零拷贝+顺序写
- 顺序写CommitLog,充分利用磁盘带宽
- sendfile零拷贝传输,减少CPU和内存拷贝
- 依赖PageCache,减少磁盘IO
3. 批量与压缩
- 生产端批量聚合,一次发送多条
- 启用压缩(lz4/zstd),网络带宽换吞吐
- 消费端批量拉取,减少请求次数
4. 异步化设计
- 异步刷盘,不每条消息都fsync
- 异步复制,Leader收到立即返回,Follower后台同步
- 可靠性场景用同步复制,性能场景用异步
5. 内存池与对象复用
- 避免频繁GC,用对象池复用Message对象
- Netty内存池管理ByteBuf
- 堆外内存减少JVM压力
6. 存储分层
- 热数据PageCache,冷数据落盘
- 分层存储,老数据迁移到便宜存储
- 支持TTL自动清理
7. 高可用保障
- 多副本机制,ISR动态调整
- Controller故障自动切换
- 跨机房容灾部署
一句话总结:分区打散、顺序写盘、零拷贝传输、批量压缩、异步刷盘、内存池化。每一点提升几倍,组合起来就是百万级。