Skip to content

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

生成时间:2026-05-21 21:46 | 共10题


【面试题】1. Kafka的ISR机制是什么?为什么说它是Kafka可靠性的核心? ​

【标准答案】ISR就是In-Sync Replicas,即"同步副本集"。Kafka每个Partition有一个Leader和多个Follower,ISR就是那些和Leader保持同步的副本集合(包括Leader自身)。

核心机制:Follower通过不断从Leader拉取数据来追赶进度,如果一个Follower落后太多——超过了replica.lag.time.max.ms(默认10秒),就会被踢出ISR,等它追上来了再加回来。

为什么说它是可靠性核心?因为Kafka的acks机制依赖ISR:

  • acks=all时,消息必须被ISR里所有副本确认才算写入成功。这意味着只要ISR里至少有一个存活副本,数据就不会丢。
  • ISR是动态伸缩的——不健康的Follower自动剔除,不影响写入性能;恢复后再加入,又保证了冗余。

跟"等所有副本确认"相比,ISR方案兼顾了可靠性和可用性:不会因为一个慢节点卡住整个写入。面试时加一句:ISR + acks=all + min.insync.replicas=2,这是Kafka生产环境最推荐的可靠性黄金组合。


【面试题】2. RabbitMQ的死信队列(DLX)是什么?哪些场景下消息会进入死信队列? ​

【标准答案】死信队列就是RabbitMQ中无法被正常消费的消息的"归宿"。消息变成死信有三种情况:

  1. 消费端NACK且requeue=false:消费者显式拒绝且不重新入队
  2. 消息TTL过期:消息设置了过期时间,到时间了还没被消费
  3. 队列达到最大长度:队列设置了x-max-length,满了之后最早的消息被挤掉变成死信

怎么用?给队列设置x-dead-letter-exchange和x-dead-letter-routing-key参数,消息变成死信后自动路由到指定Exchange,再绑定到一个专门的死信队列。

经典应用场景:

  • 延迟队列:消息设TTL → 过期进DLX → DLX绑定的队列就是延迟消费的队列,这是RabbitMQ做延迟任务的标准方案
  • 异常处理:消费失败的消息不丢弃,进死信队列后人工排查或重试
  • 订单超时取消:下单时发一条TTL=30分钟的消息,30分钟后进DLX触发取消逻辑

注意:死信队列不是RabbitMQ的内置队列类型,而是通过参数配置实现的机制,面试时别搞混。


【面试题】3. RocketMQ的事务消息是怎么实现的?和本地消息表方案比有什么优劣? ​

【标准答案】RocketMQ事务消息采用"两阶段提交+回查"机制:

执行流程:

  1. 生产者先发一条"半消息"(Half Message)到Broker,这条消息对消费者不可见
  2. Broker收到后半消息存储成功,返回确认
  3. 生产者执行本地事务
  4. 根据本地事务结果,发送Commit或Rollback给Broker
  5. Commit → 消息对消费者可见;Rollback → 消息删除

关键——回查机制:如果Broker没收到第4步的确认(比如生产者宕机了),Broker会定期回查生产者的本地事务状态,最多回查15次。生产者需要实现checkLocalTransaction接口返回事务状态。

对比本地消息表:

维度事务消息本地消息表
额外存储不需要需要业务库里建消息表
一致性保证Broker原生支持依赖定时任务扫描补偿
侵入性实现回查接口写消息表+补偿逻辑
适用场景用RocketMQ时优先任何MQ都能用

面试总结:能用RocketMQ事务消息就用它,代码更干净,不需要额外表。如果公司用的不是RocketMQ,或者团队对RocketMQ不熟,本地消息表是更通用的兜底方案。


【面试题】4. 消息消费的幂等性怎么保证?生产环境中你用什么方案? ​

【标准答案】幂等性就是"同一条消息消费多次和消费一次效果一样"。为什么需要?因为网络抖动、Rebalance、生产者重试都可能导致消息重复投递。

三大主流方案:

  1. 唯一ID + 数据库去重表

    • 每条消息带全局唯一ID(业务ID或消息ID)
    • 消费前查去重表,存在就跳过
    • 消费成功后把ID写入去重表
    • 注意:查和写必须原子性,可以用唯一索引实现——插入成功才消费,主键冲突就跳过
  2. Redis的SETNX

    • 消费前用SETNX message_id 1判断
    • 成功则消费,失败则跳过
    • 优势:性能高;劣势:Redis和数据库的一致性需要额外处理
    • 实战技巧:SETNX设过期时间,避免KEY堆积
  3. 业务状态机天然幂等

    • 比如订单状态从"已支付"→"已发货",用UPDATE order SET status='shipped' WHERE id=? AND status='paid'
    • 状态机流转是天然幂等的,重复执行影响行数为0
    • 这是最高效的方案,能从业务设计上解决就不需要额外中间件

生产环境建议:优先用业务状态机保证幂等,覆盖不了的场景用数据库唯一索引兜底。Redis方案适合高并发但对一致性要求不那么严的场景。


【面试题】5. Kafka的Exactly-Once语义是怎么实现的?为什么说它是端到端的? ​

【标准答案】Kafka的Exactly-Once解决的是"消息既不丢也不重复"的问题,分三层实现:

1. 幂等Producer(单分区内幂等)

  • 开启enable.idempotence=true
  • Producer给每条消息分配一个Sequence Number
  • Broker根据<PID, Partition, SeqNum>去重
  • 作用:单分区内保证不重复,解决的是Producer重试导致的重复写入

2. 事务(跨分区的原子写入)

  • Producer设置transactional.id
  • 开启事务后,一批消息要么全部Commit,要么全部Abort
  • 消费者设置isolation.level=read_committed,只读已提交的消息
  • 作用:保证"消费→处理→写入"整个流程的原子性

3. Consumer端的Offset提交

  • 事务中把消息写入和Offset提交绑在一起,要么都成功要么都回滚
  • 这就是"端到端"的含义——从消费到处理到再写入,全链路原子性

为什么叫端到端?因为不是只保证写入端幂等,而是从Consume → Process → Produce → Commit Offset整个闭环都保证Exactly-Once。以前的方案要么只管写入端(幂等Producer),要么只管消费端(手动提交Offset),Kafka事务把两端串起来了。

面试注意:Exactly-Once仅限于Kafka内部闭环,如果涉及到外部系统(写MySQL、调RPC),Kafka自身无法保证,需要业务侧配合幂等。


【面试题】6. RabbitMQ的Exchange四种类型有什么区别?分别适合什么场景? ​

【标准答案】RabbitMQ的Exchange负责消息路由,四种类型决定了不同的匹配规则:

1. Direct Exchange

  • 精确匹配routing key
  • 消息的routing key == 队列绑定的binding key → 投递
  • 场景:点对点通信、日志级别路由(error/warn/info各绑一个队列)

2. Fanout Exchange

  • 完全不看routing key,广播到所有绑定队列
  • 场景:群发通知、事件广播(如下单后同时通知库存系统+积分系统+物流系统)

3. Topic Exchange

  • 支持通配符匹配routing key:*匹配一个单词,#匹配零或多个单词
  • 例如:order.*匹配order.create和order.cancel;order.#匹配order.create.success
  • 场景:多维度路由、发布订阅模式的灵活版本

4. Headers Exchange

  • 不看routing key,匹配消息头(headers)的键值对
  • x-match=all表示所有header都要匹配,x-match=any表示任一匹配即可
  • 场景:需要多条件组合路由,且条件不适合编码到routing key中时

面试口诀:精确匹配用Direct,广播用Fanout,模式匹配用Topic,头匹配用Headers。90%的业务场景Direct和Topic就搞定了,Headers几乎用不到。


【面试题】7. 消息积压了怎么办?生产环境的应急处理流程是什么? ​

【标准答案】消息积压是消息中间件最常见的事故场景,处理分三步:

第一步:紧急止损

  • 先确认消费端是否正常——是不是有服务挂了、数据库慢查、下游超时
  • 如果是消费端故障,先修服务,这是治本
  • 如果消费端没问题但就是处理不过来,进入第二步

第二步:快速扩容消费能力

  • 增加消费者实例数(水平扩容)
  • 但注意:Kafka中消费者数不能超过分区数,分区是消费并行度的上限
  • 如果分区数不够,紧急扩分区(Kafka支持,但新分区只消费新消息)
  • RabbitMQ可以动态增加队列和消费者

第三步:临时方案——转发+旁路消费

  • 如果以上还不够,新建一个临时Topic/Queue,写一个转发程序把积压消息快速搬运过去
  • 临时Topic多设几个分区,起一批消费者并行消费
  • 消费完再恢复原架构

预防措施(面试加分):

  • 监控消费延迟(Consumer Lag),设阈值告警
  • 消费端做好限流降级,非核心逻辑异步化
  • 合理规划分区数,留足消费并行度余量
  • 消费逻辑避免慢操作(大事务、外部调用)

面试总结:先查因、再扩容、后旁路。别上来就加机器,先搞清楚为什么积压。


【面试题】8. Kafka为什么这么快?从存储和网络两个角度解释。 ​

【标准答案】Kafka的高性能是架构设计的结果,不是某个单点优化:

存储层面:

  1. 顺序写:消息只追加到日志文件末尾,不随机写。磁盘顺序写速度可达600MB/s,随机写只有100KB/s,差了三个数量级。
  2. PageCache:Kafka大量利用操作系统的页缓存,写入时先写PageCache,由OS异步刷盘;读取时直接从PageCache命中,不经过用户态。
  3. 零拷贝:消费者拉消息时用sendfile()系统调用,数据从磁盘→PageCache→网卡缓冲区,跳过了用户态拷贝,减少了2次数据拷贝和2次上下文切换。
  4. 批量写入+压缩:Producer攒一批消息再发,减少IO次数;支持gzip/snappy/lz4压缩,减少网络和磁盘IO。

网络层面:

  1. 批量发送:Producer的linger.ms和batch.size参数控制攒批,减少网络请求次数
  2. Reactor网络模型:Broker用1个Acceptor线程+多个Processor线程处理网络连接,非阻塞NIO
  3. 分区并行:Topic分多个Partition,不同Partition可被不同Broker处理,天然并行

一句话总结:顺序写+零拷贝+PageCache解决了磁盘IO瓶颈,批量+压缩+分区并行解决了网络和吞吐瓶颈。这不是某个优化点,而是一整套为日志型数据设计的架构选择。


【面试题】9. RabbitMQ、Kafka、RocketMQ三个怎么选?什么场景用什么? ​

【标准答案】选型看三个维度:吞吐量、功能丰富度、运维复杂度。

RabbitMQ:

  • 吞吐量:万级TPS
  • 优势:功能最丰富,Exchange路由灵活、死信队列、延迟队列开箱即用,管理界面友好
  • 劣势:吞吐量有上限,Erlang技术栈小众,集群扩容麻烦
  • 适用:中小规模业务系统、需要复杂路由、对吞吐量要求不极端的场景
  • 典型场景:订单状态变更通知、异步任务调度、企业内部系统集成

Kafka:

  • 吞吐量:百万级TPS
  • 优势:极致吞吐、分布式架构天然水平扩展、生态完善(Streams/Connect/KSQL)
  • 劣势:功能相对简单,没有延迟消息、没有死信队列原生支持、运维较重
  • 适用:大数据场景、日志收集、流式计算、高吞吐事件流
  • 典型场景:用户行为日志采集、实时数仓管道、消息量巨大的事件驱动架构

RocketMQ:

  • 吞吐量:十万级TPS
  • 优势:事务消息、延迟消息、消息过滤(Tag/SQL92)等企业级功能齐全,Java技术栈好维护
  • 劣势:社区不如Kafka活跃,生态不如Kafka丰富
  • 适用:电商/金融等对消息可靠性要求高的业务场景
  • 典型场景:分布式事务消息、订单超时取消、金融级消息通知

面试一句话:日志流式数据选Kafka,复杂路由中小规模选RabbitMQ,电商金融高可靠选RocketMQ。没有银弹,看业务场景。


【面试题】10. Kafka Consumer Group的Rebalance机制是什么?有什么问题?怎么优化? ​

【标准答案】Rebalance是Consumer Group内的分区重分配过程——当一个消费者加入或离开Group时,Kafka需要把分区重新分配给存活的消费者。

触发条件:

  1. 消费者加入Group(新增实例)
  2. 消费者离开Group(宕机或主动退出)
  3. 订阅的Topic分区数变化
  4. 消费者心跳超时(session.timeout.ms)或处理超时(max.poll.interval.ms)

Rebalance的问题:

  1. Stop-The-World:Rebalance期间所有消费者停止消费,造成短暂消费暂停
  2. 重复消费:Rebalance后Offset可能回退,导致消息重复
  3. 频繁Rebalance:消费者处理慢导致max.poll.interval.ms超时,被踢出后又触发Rebalance,形成恶性循环
  4. 分配不均:默认的Range分配器可能导致前面的消费者分到更多分区

优化方案:

  1. 调参避免误触发

    • session.timeout.ms适当调大(如25s),避免网络抖动误判
    • heartbeat.interval.ms设为session.timeout.ms的1/3
    • max.poll.interval.ms根据业务处理时间调大(如5分钟)
  2. 使用Sticky分配器

    • partition.assignment.strategy=org.apache.kafka.clients.consumer.StickyAssignor
    • Rebalance时尽量保持原有分配不变,减少分区迁移
  3. Kafka 2.4+的Cooperative Sticky协议

    • 增量Rebalance,只迁移需要变动的分区
    • 不再Stop-The-World,其余消费者正常消费
    • 配置:partition.assignment.strategy=org.apache.kafka.clients.consumer.CooperativeStickyAssignor
  4. 消费者健康检查

    • 业务处理和心跳线程分离,长时间处理不影响心跳
    • 避免在消费线程做耗时操作

面试总结:Rebalance是Consumer Group的"双刃剑"——保证故障恢复但也带来暂停。优化核心是"减少触发+减少影响",Sticky和Cooperative协议是技术手段,参数调优是兜底手段。