消息中间件面试题库(带答案)
生成时间: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是所有副本
为什么不用简单同步复制?三个原因:
- 性能问题:同步复制要求所有副本都写成功才能返回,一个慢节点拖垮整个集群。ISR允许部分副本滞后,兼顾性能和可靠性
- 灵活性:ISR动态伸缩,节点恢复后自动加入,故障后自动剔除
- 可用性:acks=all时只要ISR所有副本写入即可,ISR可以小于AR,不会因为一个挂掉的节点阻塞写入
生产环境配置建议:acks=all + min.insync.replicas=2,至少2个副本确认,既保证可靠性又不至于一个副本挂了就无法写入。
【面试题】2. RocketMQ的事务消息是如何实现分布式事务的?半消息是什么?
【标准答案】RocketMQ事务消息的核心是半消息+两阶段提交+回查机制。
半消息定义:暂时不能被消费的消息,存储在Broker但标记为"暂不可消费"状态。
实现流程:
- 发送半消息:生产者发送半消息到Broker,Broker返回发送结果
- 执行本地事务:生产者执行本地数据库事务
- 提交/回滚:根据本地事务结果,向Broker发送Commit或Rollback
- 回查机制:如果Broker没收到Commit/Rollback(比如生产者挂了),Broker会主动回查生产者的事务状态
关键点:
- 半消息放在单独的
RMQ_SYS_TRANS_HALF_TOPIC队列 - Commit后消息移到真实Topic,对消费者可见
- Rollback则丢弃消息
- 回查次数默认15次,超过丢弃
和本地消息表的区别:本地消息表方案需要定时任务扫描,事务消息是Broker主动回查,更简洁且性能更好。
【面试题】3. RabbitMQ的延迟队列有几种实现方式?各有什么优缺点?
【标准答案】两种主流方式:
方式一:TTL + 死信队列(DLX)
原理:
- 消息设置TTL过期时间
- 过期后自动进入绑定的死信队列
- 消费者监听死信队列,实现延迟消费
配置步骤:
- 创建正常队列,绑定DLX和TTL
- 消息过期后转入死信队列
- 消费者监听死信队列
优点:原生支持,无需插件 缺点:
- 不同延迟时间需要不同队列,灵活性差
- 队列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时冲突则说明已处理,直接返回成功
- 适合消息处理逻辑是数据库写入的场景
代码示例:
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:
- 复杂性:Zookeeper有一套复杂的选举和同步机制(ZAB协议),NameServer逻辑简单
- 性能瓶颈:Zookeeper所有写操作经过Leader,RocketMQ Topic数量可能很多,Zookeeper扛不住
- CAP取舍:Zookeeper保证CP,NameServer偏向AP,注册中心允许短暂不一致
NameServer的优势:
- 部署简单:无主节点,随便扩缩容
- 性能高:无选举开销,内存存储
- 可靠性够用:Broker向所有NameServer注册,一个挂了还有其他
NameServer的劣势:
- 数据短暂不一致:Broker注册信息有延迟
- 需要多节点部署保证可靠性
生产实践:NameServer至少部署3个节点,Broker配置所有NameServer地址,一个挂了不影响服务。
【面试题】10. 消息队列在高并发场景下如何防止消息丢失?完整的消息可靠性链路是怎么做的?
【标准答案】消息丢失可能发生在三个环节:生产端、Broker端、消费端。需要三端配合。
生产端防丢失:
确认机制
- RabbitMQ:开启Confirm模式,waitForConfirms等待确认
- Kafka:acks=all
- RocketMQ:同步发送,捕获SendResult
失败重试
- 同步重试3次,间隔递增
- 重试失败写入本地数据库,定时任务补偿
事务消息
- 重要数据用事务消息(RocketMQ)
- 或者本地消息表方案
Broker端防丢失:
持久化配置
- RabbitMQ:消息持久化(delivery_mode=2)+ 队列持久化 + Exchange持久化
- Kafka:log.flush.interval.messages控制刷盘频率(一般交给操作系统)
- RocketMQ:CommitLog和ConsumeQueue默认持久化
多副本机制
- Kafka:acks=all + min.insync.replicas >= 2
- RabbitMQ:镜像队列或仲裁队列
- RocketMQ:主从同步复制
消费端防丢失:
手动确认
- RabbitMQ:autoAck=false,处理成功后手动basicAck
- Kafka:enable.auto.commit=false,处理成功后手动提交offset
- RocketMQ:返回ConsumeConcurrentlyStatus.CONSUME_SUCCESS
异常处理
- 处理失败不确认,让消息重新投递
- 重试次数超过阈值进入死信队列
完整链路总结:
生产端:同步发送 + 确认机制 + 失败本地持久化
↓
Broker:持久化 + 多副本同步
↓
消费端:手动ACK + 失败重试 + 死信兜底监控补充:
- 生产端监控发送失败率
- Broker监控磁盘使用和副本同步延迟
- 消费端监控消费延迟和重试队列积压
面试加分点:提到消息丢失是概率事件,核心业务要结合业务侧的定时对账机制做兜底。