Skip to content

消息中间件面试题库 ​

生成时间:2026-05-22 00:03 | 题目数:10


【面试题】1. RabbitMQ的四种Exchange类型分别适用于什么场景?请结合实际业务举例说明。 ​

【标准答案】

四种Exchange类型:

  1. Direct Exchange:精确匹配routing key。适合点对点场景,比如订单系统发送消息给特定订单处理服务,routing key就是"order.create",只有绑定了这个key的队列才能收到。

  2. Fanout Exchange:广播模式,忽略routing key,所有绑定队列都收到。适合一对多通知场景,比如用户注册后同时通知短信服务、邮件服务、积分服务。

  3. Topic Exchange:模式匹配routing key,支持*(一个词)和#(零或多个词)通配符。适合多维度路由场景,比如日志系统用log.error.payment做routing key,绑定log.error.*的队列收到所有error级别日志,绑定log.#的队列收到全部日志。

  4. Headers Exchange:根据消息头属性匹配,忽略routing key。适合需要多属性组合匹配的场景,比如根据消息头中的region和type两个字段做路由,但实际用得少,Topic Exchange基本能覆盖。

面试要点:重点说Direct和Topic,这两个最常用。Fanout说广播场景。Headers提一嘴就行,面试官知道你懂但不常用。


【面试题】2. Kafka的ISR机制是什么?为什么需要ISR而不是简单的"所有副本都同步完才算成功"? ​

【标准答案】

ISR(In-Sync Replicas)是Kafka中与Leader保持同步的副本集合,由Leader维护。

为什么不能要求所有副本同步完才算成功? 两个致命问题:

  1. 可用性差:假设3个副本,1个宕机,如果要求全部同步,那这条消息就永远无法确认,系统直接卡死。一个慢节点拖垮整个集群。
  2. 性能瓶颈:最慢的那个副本决定写入延迟,木桶效应明显。

ISR的核心设计:

  • Leader维护ISR列表,只有ISR中的副本才有资格被选为新Leader
  • 落后太多的副本会被踢出ISR(replica.lag.time.max.ms,默认10秒没拉取就踢出)
  • 被踢出的副本追上后可以重新加入ISR
  • acks=all的语义是"ISR中所有副本确认",不是"所有副本确认"

这就是Kafka在可靠性和可用性之间的精妙平衡:保证ISR内的副本数据一致,同时容忍少数副本掉队。配合min.insync.replicas参数(默认1),可以设置ISR最少副本数,比如设为2,意味着ISR不足2时拒绝写入,确保至少2个副本有数据。

面试加分点:提到unclean.leader.election.enable参数,默认false,禁止非ISR副本成为Leader,防止数据丢失。如果设为true,优先保证可用性但可能丢数据。


【面试题】3. RocketMQ的CommitLog + ConsumeQueue存储模型是如何设计的?为什么不像Kafka那样每个Partition一个文件? ​

【标准答案】

存储结构:

  • CommitLog:所有Topic的消息顺序写入同一个物理文件,每条消息按写入顺序分配全局offset。文件大小固定1GB,写满滚动到新文件。
  • ConsumeQueue:逻辑消费队列,每个Topic的每个Queue对应一个ConsumeQueue。存的是索引——8字节CommitLog offset + 4字节消息大小 + 8字节Tag的hashCode,共20字节一条索引。

为什么不像Kafka每个Partition一个文件?

  1. 顺序写性能极致化:所有消息写同一个CommitLog,无论多少Topic多少Queue,磁盘永远是顺序写。Kafka多Partition意味着多文件交替写,Partition多了性能下降明显。

  2. 减少文件句柄:Kafka如果有100个Topic每个16个Partition就是1600个文件,RocketMQ不管多少Topic多少Queue,CommitLog始终是一个文件在写。

  3. 劣势也明显:消费时需要两次读取——先读ConsumeQueue索引,再根据offset去CommitLog读消息体,比Kafka直接读Partition文件多一次IO。但RocketMQ通过mmap + PageCache优化,热点数据在内存中,实际影响不大。

面试关键:这个设计是RocketMQ在阿里内部海量Topic场景下的权衡结果。Kafka适合少Topic大吞吐,RocketMQ适合多Topic混合场景。


【面试题】4. 消息消费的幂等性如何保证?请给出生产环境可行的方案。 ​

【标准答案】

消息中间件至少一次投递是常态,消费端必须做幂等。三种生产级方案:

方案一:唯一消息ID + 数据库去重表

  • 生产者生成全局唯一ID(bizId + seqNo)放在消息头
  • 消费端建去重表:unique_id(唯一索引)、biz_id、status、create_time
  • 消费前先查去重表,存在则直接返回成功
  • 优点:最可靠,强一致;缺点:每次消费多一次DB查询
  • 优化:消费成功后写去重表,利用唯一索引冲突判断重复

方案二:Redis SETNX

  • 用SET messageId EX 3600做去重判断
  • 优点:性能高;缺点:Redis宕机或key过期可能重复消费
  • 适合:允许极小概率重复的非金融场景

方案三:业务状态机

  • 订单状态从"待支付"→"已支付",重复消费时判断当前状态已是"已支付",直接返回成功
  • 优点:无需额外组件;缺点:仅适合有明确状态流转的业务
  • 最推荐:业务表自带幂等语义时,优先用状态机,零额外开销

生产环境最佳实践:方案一和方案三组合使用。金融场景用去重表兜底,业务状态机做第一层快速判断。去重表定期清理(保留7天),配合定时任务对账补漏。


【面试题】5. Kafka的Exactly-Once语义是如何实现的?它解决了什么问题? ​

【标准答案】

解决的痛点:Kafka默认at-least-once,Consumer处理完消息后提交offset失败会重复消费;Producer发送消息网络超时重试会重复写入。Exactly-Once保证消息既不丢失也不重复。

三层实现机制:

  1. 幂等Producer(Idempotent Producer):

    • enable.idempotence=true,Producer分配PID(Producer ID)+ 每条消息带Sequence Number
    • Broker端对<PID, TopicPartition, SeqNum>去重,相同SeqNum的重复消息直接丢弃
    • 解决单Producer单分区的消息重复问题
    • 局限:只防重试导致的重复,跨分区的原子写入管不了
  2. 事务(Transactional):

    • transactional.id标识一个事务Producer
    • beginTransaction() → 多次send() → commitTransaction(),要么全部可见,要么全部回滚
    • Consumer端设置isolation.level=read_committed只读已提交消息
    • 解决:消费A Topic → 处理 → 写入B Topic 的端到端Exactly-Once
  3. Consumer端配合:

    • 将offset提交放在事务内,消息处理和offset提交原子完成
    • 事务恢复时,transactional.id映射到新的PID,但能通过事务日志恢复之前未完成的事务

面试重点:Exactly-Once不是万能的,它保证的是Kafka内部端到端不重复。如果你消费后写MySQL,Kafka的事务管不到MySQL,需要本地消息表或业务幂等配合。


【面试题】6. RabbitMQ如何实现延迟队列?两种方案各有什么优劣? ​

【标准答案】

方案一:TTL + DLX(经典方案)

  • 消息设置TTL(每条消息可不同),过期后自动进入绑定的死信交换机(DLX)
  • DLX再路由到实际消费队列
  • 实现:声明普通队列绑定DLX → 生产者发消息时设expiration → 消息过期进入死信队列 → 消费者消费死信队列

致命缺陷:RabbitMQ队列是FIFO,只会检查队头消息是否过期。如果先发一条TTL=1小时的消息,再发TTL=1秒的消息,那1秒的消息要等1小时的消息过期后才能被处理。同一队列中短TTL消息被长TTL消息阻塞。

解决方案:为每种TTL创建一个专用队列,但队列数膨胀。

方案二:rabbitmq_delayed_message_exchange插件

  • 安装插件后声明x-delayed-message类型的Exchange
  • 发消息时在header中设x-delay(毫秒),消息在Exchange中延迟指定时间后才路由到队列
  • 优点:不用创建多个队列,每条消息独立延迟,无阻塞问题
  • 缺点:消息存在Exchange的Mnesia表中,不支持持久化(3.x版本),大量延迟消息时性能下降

生产建议:

  • 延迟等级固定(如5秒/30秒/1分钟/5分钟)→ TTL+DLX + 每个等级一个队列,最稳定
  • 延迟时间不固定且量不大 → 插件方案,开发简单
  • 海量延迟消息 → 考虑RocketMQ的18个延迟等级或自研时间轮

【面试题】7. Consumer Group重平衡(Rebalance)是什么?Kafka和RocketMQ的重平衡有什么区别? ​

【标准答案】

重平衡本质:Consumer Group中的消费者与分区的分配关系重新调整。触发条件:消费者加入/退出、订阅Topic分区数变化、订阅Topic变化。

Kafka重平衡:

  • 由Coordinator(Group对应的Broker)主导
  • 三代协议演进:
    • Eager协议:所有消费者先撤销全部分配,再重新分配。Stop-The-World,影响大。
    • CooperativeSticky协议:增量重平衡,尽量保持原有分配不变,只调整变化的分区。但第一次仍需全量重平衡。
    • KIP-429渐进式:真正意义上的增量,新增消费者时只迁移必要分区。
  • 重平衡期间消费者无法消费,称为"重平衡风暴"
  • 调优:session.timeout.ms(心跳超时)、max.poll.interval.ms(处理超时)、heartbeat.interval.ms

RocketMQ重平衡:

  • 由每个Consumer自己计算分配方案(无中心化),每20秒检查一次
  • 分配策略默认AllocateMessageQueueAveragely(平均分配)
  • 没有Kafka那样的全局Stop-The-World,但可能出现短暂重复消费(两个消费者短暂分配到同一Queue)
  • RocketMQ 5.x引入Pop模式,一个Queue可被多个消费者拉取,减少重平衡影响

面试关键差异:Kafka重平衡是协调器驱动的集中式方案,一致性更强但有全局暂停;RocketMQ是分布式方案,可用性更好但一致性稍弱。Kafka重平衡是面试高频考点。


【面试题】8. 百万级消息积压如何应急处理?给出完整方案。 ​

【标准答案】

第一步:快速止血

  • 先确认消费端是否有异常(OOM、DB死锁、外部接口超时),修复根因
  • 如果消费端正常但就是慢,临时扩容消费者数量
  • 注意:Kafka消费者数不能超过分区数,扩容消费者的同时可能需要扩分区

第二步:紧急扩容消费能力

  • Kafka场景:新增临时Topic,分区数设为原来的N倍 → 用临时程序把积压消息快速转发到新Topic → 扩容消费组消费新Topic → 消费完切回原Topic
  • RabbitMQ场景:新增N个临时队列绑定到原Exchange → 扩容消费者消费临时队列 → 消费完取消绑定
  • RocketMQ场景:扩容消费者实例,但注意每个Queue只能被一个消费者消费,Queue数是瓶颈

第三步:消费端提速优化

  • 批量消费:一次拉取500条批量处理(max.poll.records)
  • 异步处理:消息拉取后丢线程池,不等处理完就提交offset(注意幂等)
  • 跳过非关键处理:先写DB核心字段,非核心逻辑异步补齐
  • 临时关闭非必要校验和日志

第四步:恢复后复盘

  • 根因:是突发流量还是消费能力不足?监控告警是否及时?
  • 常设防护:消费延迟监控告警、自动扩容策略、消息TTL上限(避免无限积压)

面试加分:提到"消息积压时绝对不能直接重启消费者",可能导致更严重的重平衡风暴。提到如果消息有TTL且即将过期,优先处理即将过期的消息。


【面试题】9. Kafka零拷贝和RocketMQ的mmap分别是什么?为什么它们能提升性能? ​

【标准答案】

传统IO流程(4次拷贝): 磁盘 → 内核PageCache → 用户空间Buffer → Socket Buffer → 网卡缓冲区 → 网络

Kafka零拷贝(sendfile):

  • 调用sendfile()系统调用,数据从PageCache直接到网卡,跳过用户空间
  • 拷贝路径:磁盘 → PageCache → 网卡缓冲区 → 网络(2次拷贝)
  • 前提:消息已经在PageCache中(Kafka顺序写保证热点数据在PageCache)
  • 适用场景:Consumer拉取消息,数据不需要在应用层做任何处理

RocketMQ的mmap(内存映射):

  • 调用mmap()将CommitLog文件映射到用户空间虚拟内存地址
  • 读写消息通过操作内存地址完成,无需read/write系统调用
  • 拷贝路径:磁盘 → PageCache → 用户空间映射区(2次拷贝,但少了一次内核态到用户态的CPU拷贝)
  • 优势:Producer写入和Consumer读取都能用,灵活性高
  • RocketMQ还做了优化:预分配CommitLog文件、内存预热(mlock锁定PageCache防swap)、TransientStorePool(堆外内存写缓冲,异步刷盘)

对比:

  • sendfile更快(1次CPU拷贝),但只能在内核态转发,应用层无法处理消息内容
  • mmap灵活(应用层可操作数据),多1次CPU拷贝,但Producer端写入也受益
  • Kafka用sendfile因为Consumer只是拉取转发,RocketMQ用mmap因为需要支持消息过滤、Tag查询等应用层逻辑

面试要点:零拷贝不是"零次拷贝",是减少CPU参与的拷贝次数。本质是避免数据在内核空间和用户空间之间来回搬。


【面试题】10. RabbitMQ、Kafka、RocketMQ、Pulsar四款消息中间件如何选型?给出对比维度和决策建议。 ​

【标准答案】

核心对比:

维度RabbitMQKafkaRocketMQPulsar
吞吐量万级百万级十万级百万级
延迟μs级ms级ms级ms级
可靠性高(镜像队列/仲裁队列)高(副本+ISR)高(主从+DLedger)高(BookKeeper)
消息顺序单队列有序单分区有序单Queue有序单分区有序
延迟消息支持(插件/TTL+DLX)不支持支持(18等级)原生支持
事务消息不支持支持(Exactly-Once)支持(半消息+回查)支持
消息过滤不支持不支持支持(Tag/SQL92)支持
协议支持AMQP/STOMP/MQTT自定义协议自定义协议自定义+KoP兼容Kafka
运维复杂度低中中高
社区生态成熟(Java/Go/Python)最活跃阿里系增长中

决策建议:

  1. 选RabbitMQ:中小规模业务、路由规则复杂(多种Exchange)、需要微秒级延迟、团队Java/Spring技术栈、快速上线优先。死磕AMQP协议的场景。

  2. 选Kafka:大数据场景为主(日志收集、实时计算管道、事件流)、超高吞吐、需要Exactly-Once、生态集成要求高(Flink/Spark/ClickHouse)。LinkedIn/Meta同款架构。

  3. 选RocketMQ:电商/金融业务、需要事务消息和延迟消息、消息过滤需求强、国内团队技术支持方便。阿里双十一验证过的可靠性。

  4. 选Pulsar:多租户隔离需求、需要存算分离架构(Broker无状态 + BookKeeper存储层)、需要同时支持队列和流两种模式、长期架构演进考虑。

面试关键:不要说"某某最好",要说出"什么场景选什么,为什么"。选型本质是trade-off,面试官想听的是你对各组件优劣的理解深度,不是背参数表。