Skip to content

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

生成时间:2026-05-21 21:55


【面试题】1. Kafka的ISR机制是什么?为什么需要ISR而不是简单的同步复制? ​

【标准答案】ISR全称In-Sync Replicas,是Kafka动态维护的一组与Leader保持同步的副本集合。

核心机制:

  • ISR里的副本与Leader差距在replica.lag.time.max.ms以内
  • ISR之外的副本叫OSR(Out-of-Sync Replicas)
  • AR = ISR + OSR,AR是所有副本

为什么不用简单同步复制?三个原因:

  1. 性能问题:同步复制要求所有副本都写成功才能返回,一个慢节点拖垮整个集群。ISR允许部分副本滞后,兼顾性能和可靠性
  2. 灵活性:ISR动态伸缩,节点恢复后自动加入,故障后自动剔除
  3. 可用性:acks=all时只要ISR所有副本写入即可,ISR可以小于AR,不会因为一个挂掉的节点阻塞写入

生产环境配置建议:acks=all + min.insync.replicas=2,至少2个副本确认,既保证可靠性又不至于一个副本挂了就无法写入。


【面试题】2. RocketMQ的事务消息是如何实现分布式事务的?半消息是什么? ​

【标准答案】RocketMQ事务消息的核心是半消息+两阶段提交+回查机制。

半消息定义:暂时不能被消费的消息,存储在Broker但标记为"暂不可消费"状态。

实现流程:

  1. 发送半消息:生产者发送半消息到Broker,Broker返回发送结果
  2. 执行本地事务:生产者执行本地数据库事务
  3. 提交/回滚:根据本地事务结果,向Broker发送Commit或Rollback
  4. 回查机制:如果Broker没收到Commit/Rollback(比如生产者挂了),Broker会主动回查生产者的事务状态

关键点:

  • 半消息放在单独的RMQ_SYS_TRANS_HALF_TOPIC队列
  • Commit后消息移到真实Topic,对消费者可见
  • Rollback则丢弃消息
  • 回查次数默认15次,超过丢弃

和本地消息表的区别:本地消息表方案需要定时任务扫描,事务消息是Broker主动回查,更简洁且性能更好。


【面试题】3. RabbitMQ的延迟队列有几种实现方式?各有什么优缺点? ​

【标准答案】两种主流方式:

方式一:TTL + 死信队列(DLX)

原理:

  • 消息设置TTL过期时间
  • 过期后自动进入绑定的死信队列
  • 消费者监听死信队列,实现延迟消费

配置步骤:

  1. 创建正常队列,绑定DLX和TTL
  2. 消息过期后转入死信队列
  3. 消费者监听死信队列

优点:原生支持,无需插件 缺点:

  • 不同延迟时间需要不同队列,灵活性差
  • 队列FIFO特性导致先入队的长TTL消息阻塞后入队的短TTL消息

方式二:rabbitmq_delayed_message_exchange插件

原理:消息在Exchange层延迟,不进入队列,到期后路由到目标队列。

优点:

  • 一个Exchange支持不同延迟时间
  • 精确到毫秒级延迟
  • 无队头阻塞问题

缺点:

  • 需要安装插件
  • 延迟消息存放在磁盘,性能有损耗
  • 不支持持久化的消息会丢失

选型建议:延迟时间固定且种类少用TTL+DLX,延迟时间多变用插件方案。


【面试题】4. 消息队列如何保证消息的顺序性?Kafka和RocketMQ的实现有什么区别? ​

【标准答案】顺序性分两个层次:全局有序和分区有序。

全局有序:整个Topic下所有消息有序。实现方式是单队列+单消费者,但吞吐量极低,生产环境很少用。

分区有序:同一分区(Partition/Queue)内消息有序。这是主流方案。

Kafka实现:

  • Producer发送时指定Partition Key,相同Key的消息路由到同一分区
  • 同一分区内消息按写入顺序存储
  • 同一Consumer Group内,一个分区只能被一个消费者消费
  • 关键配置:max.in.flight.requests.per.connection=1 + enable.idempotence=true(防止重试乱序)

RocketMQ实现:

  • 发送时指定MessageQueueSelector,相同OrderId选择同一Queue
  • 消费者端使用MessageListenerOrderly接口,单线程消费同一Queue
  • 如果使用MessageListenerConcurrently,多线程消费会乱序

核心区别:

  • Kafka分区粒度控制,RocketMQ Queue粒度
  • RocketMQ的顺序消息默认会锁定Queue,异常时不会立即触发重平衡,更稳定
  • Kafka顺序消息场景下,消费者宕机会触发重平衡,可能导致短暂乱序

实战建议:业务上用分区有序足够,比如订单创建→支付→发货,同一订单ID保证有序即可。


【面试题】5. Kafka的消息积压怎么处理?线上已经积压了几千万条消息怎么办? ​

【标准答案】分三个阶段处理:

阶段一:快速定位原因

  • 消费者处理慢:代码有性能瓶颈、下游服务慢
  • 消费能力不足:消费者数量少于分区数
  • 消费者故障:频繁Rebalance或OOM

阶段二:应急处理方案

方案1:临时扩容消费者

  • 增加消费者实例数量到分区数上限
  • 修改max.poll.records增加单次拉取量
  • 下游支持批量处理则开启批量消费

方案2:新建Topic + 转发

  • 原消费者不做业务处理,快速转发到新Topic
  • 新Topic分区数扩容(比如原来10个扩到50个)
  • 新消费者组消费新Topic,并行度大幅提升

方案3:直接丢弃非核心数据

  • 如果是日志类非核心数据,直接跳过积压部分
  • 重置offset到最新位置

阶段三:根本解决

  • 消费者做异步处理:接消息后写入本地队列,异步线程池处理
  • 下游做批量接口:数据库批量写入、HTTP批量调用
  • 本地缓存+定时任务:先存本地,定时批量同步

预防措施:

  • 监控消费延迟(lag),设置告警阈值
  • 压测确定消费能力上限,提前规划容量
  • 关键业务做降级开关,高峰期跳过非核心逻辑

【面试题】6. RabbitMQ的Exchange类型有哪些?分别适用什么场景? ​

【标准答案】四种类型:

1. Direct Exchange(直连交换机)

路由规则:消息的Routing Key与Binding Key完全匹配

适用场景:

  • 点对点消息
  • 消息路由到特定队列
  • 简单的路由逻辑

示例:订单创建消息路由到order.create队列

2. Fanout Exchange(扇形交换机)

路由规则:忽略Routing Key,广播到所有绑定队列

适用场景:

  • 广播消息
  • 多个系统同时处理同一消息
  • 缓存更新通知

示例:商品价格变更,通知搜索系统、推荐系统、统计分析系统

3. Topic Exchange(主题交换机)

路由规则:支持通配符匹配

  • *匹配一个单词
  • #匹配零或多个单词

适用场景:

  • 多维度路由
  • 灵活的消息分类
  • 日志收集场景

示例:user.*.created匹配user.account.created、user.profile.created

4. Headers Exchange(头交换机)

路由规则:基于消息Header属性匹配,忽略Routing Key

适用场景:

  • 复杂条件路由
  • 多条件组合匹配
  • 用得很少,性能差

实际生产中:Direct和Topic最常用,Fanout适合广播,Headers基本不用。


【面试题】7. 消息队列如何实现消费端的幂等性?有哪些方案? ​

【标准答案】幂等性的本质是:同一条消息消费多次,结果和消费一次一样。

方案一:数据库唯一索引

实现:

  • 消息ID作为业务表唯一索引
  • INSERT时冲突则说明已处理,直接返回成功
  • 适合消息处理逻辑是数据库写入的场景

代码示例:

java
try {
    orderMapper.insert(order); // orderId唯一索引
} catch (DuplicateKeyException e) {
    return; // 已处理,幂等返回
}

方案二:独立的去重表

实现:

  • 单独建表存储已消费的消息ID
  • 消费前先查询,已存在则跳过
  • 查询和插入要在同一事务里

优点:不侵入业务表结构 缺点:多一次数据库操作

方案三:Redis SETNX

实现:

  • 消费前执行SETNX msgId 1
  • 返回1说明首次消费,继续处理
  • 返回0说明已消费,跳过
  • 设置过期时间防止内存泄漏

优点:性能高 缺点:Redis和业务操作需要保证原子性

方案四:Token机制

实现:

  • 生产者发送消息前,先下发一个Token
  • 消费者消费时携带Token,服务端校验后立即失效
  • 适合消息是触发特定操作的场景

方案五:状态机幂等

实现:

  • 业务状态机天然幂等
  • 订单:待支付→已支付,重复消费不会改变状态

生产环境推荐:

  • 数据库写入场景:唯一索引最简单
  • 高并发场景:Redis去重 + 数据库兜底
  • 状态机场景:直接利用状态约束

【面试题】8. Kafka的acks参数有什么作用?acks=0/1/all分别是什么意思?如何选择? ​

【标准答案】acks控制Producer发送消息后,Broker返回确认的时机。

acks=0

  • Producer发送后不等待Broker确认,直接返回成功
  • 可靠性最低,可能丢消息
  • 延迟最低,吞吐量最高
  • 适用场景:日志收集、监控数据,允许少量丢失

acks=1(默认)

  • Leader写入成功即返回确认
  • 不等待Follower同步
  • Leader挂了但Follower还没同步,消息丢失
  • 可靠性中等,延迟中等
  • 适用场景:大多数业务场景,平衡性能和可靠性

acks=all(或-1)

  • Leader + ISR所有副本写入成功才返回确认
  • 配合min.insync.replicas保证最小副本数
  • 可靠性最高,延迟最高
  • 适用场景:金融、订单等核心业务

实际配置建议:

acks=all
min.insync.replicas=2
replication.factor=3

至少3副本,ISR中至少2个写入成功,一个副本挂了不影响服务。

注意点:

  • acks=all时如果ISR只剩1个副本,等价于acks=1,仍可能丢消息
  • 所以必须配置min.insync.replicas > 1

【面试题】9. RocketMQ的NameServer为什么不用Zookeeper?相比Zookeeper有什么优势? ​

【标准答案】这是RocketMQ架构设计的核心特点。

NameServer的设计理念:

  • 无状态的节点,每个节点独立运行
  • Broker定时向所有NameServer发送心跳注册
  • Producer/Consumer从NameServer获取Topic路由信息
  • 各NameServer之间不通信,数据可能有短暂不一致

为什么不用Zookeeper:

  1. 复杂性:Zookeeper有一套复杂的选举和同步机制(ZAB协议),NameServer逻辑简单
  2. 性能瓶颈:Zookeeper所有写操作经过Leader,RocketMQ Topic数量可能很多,Zookeeper扛不住
  3. CAP取舍:Zookeeper保证CP,NameServer偏向AP,注册中心允许短暂不一致

NameServer的优势:

  • 部署简单:无主节点,随便扩缩容
  • 性能高:无选举开销,内存存储
  • 可靠性够用:Broker向所有NameServer注册,一个挂了还有其他

NameServer的劣势:

  • 数据短暂不一致:Broker注册信息有延迟
  • 需要多节点部署保证可靠性

生产实践:NameServer至少部署3个节点,Broker配置所有NameServer地址,一个挂了不影响服务。


【面试题】10. 消息队列在高并发场景下如何防止消息丢失?完整的消息可靠性链路是怎么做的? ​

【标准答案】消息丢失可能发生在三个环节:生产端、Broker端、消费端。需要三端配合。

生产端防丢失:

  1. 确认机制

    • RabbitMQ:开启Confirm模式,waitForConfirms等待确认
    • Kafka:acks=all
    • RocketMQ:同步发送,捕获SendResult
  2. 失败重试

    • 同步重试3次,间隔递增
    • 重试失败写入本地数据库,定时任务补偿
  3. 事务消息

    • 重要数据用事务消息(RocketMQ)
    • 或者本地消息表方案

Broker端防丢失:

  1. 持久化配置

    • RabbitMQ:消息持久化(delivery_mode=2)+ 队列持久化 + Exchange持久化
    • Kafka:log.flush.interval.messages控制刷盘频率(一般交给操作系统)
    • RocketMQ:CommitLog和ConsumeQueue默认持久化
  2. 多副本机制

    • Kafka:acks=all + min.insync.replicas >= 2
    • RabbitMQ:镜像队列或仲裁队列
    • RocketMQ:主从同步复制

消费端防丢失:

  1. 手动确认

    • RabbitMQ:autoAck=false,处理成功后手动basicAck
    • Kafka:enable.auto.commit=false,处理成功后手动提交offset
    • RocketMQ:返回ConsumeConcurrentlyStatus.CONSUME_SUCCESS
  2. 异常处理

    • 处理失败不确认,让消息重新投递
    • 重试次数超过阈值进入死信队列

完整链路总结:

生产端:同步发送 + 确认机制 + 失败本地持久化
       ↓
Broker:持久化 + 多副本同步
       ↓
消费端:手动ACK + 失败重试 + 死信兜底

监控补充:

  • 生产端监控发送失败率
  • Broker监控磁盘使用和副本同步延迟
  • 消费端监控消费延迟和重试队列积压

面试加分点:提到消息丢失是概率事件,核心业务要结合业务侧的定时对账机制做兜底。