Skip to content

消息中间件面试题库(第N轮) ​

生成时间:2026-05-21 23:51


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

【面试题】1. Kafka 的消息发送流程是怎样的?ACK 机制如何保证消息不丢失? ​

【标准答案】

消息发送流程分三步:

  1. Producer 发送:Producer 将消息发送到 Broker,消息写入 Leader 副本的 Log 文件
  2. 副本同步:Follower 副本从 Leader 拉取消息进行同步
  3. 响应确认:Leader 收到 ACK 后,Producer 收到发送成功响应

ACK 机制三级别:

  • acks=0:Producer 不等待 ACK,性能最高但可能丢失消息
  • acks=1:Leader 写入成功即返回 ACK,可能丢失(Leader 挂了,Follower 未同步)
  • acks=all/-1:Leader 等待所有 ISR 副本同步成功才返回,最可靠但性能最低

生产环境推荐:acks=all + min.insync.replicas=2,保证至少 2 个副本写入成功,兼顾可靠性和性能。


【面试题】2. RabbitMQ 的死信队列是什么?有哪些应用场景? ​

【标准答案】

死信队列(DLX,Dead Letter Exchange)是存储"无法被正常消费的消息"的特殊队列。

消息成为死信的三种情况:

  1. 消息被拒绝(basic.reject/basic_nack)且 requeue=false
  2. 消息过期(TTL 到期)
  3. 队列满了,最早的消息被挤出

配置方式:

java
Map<String, Object> args = new HashMap<>();
args.put("x-dead-letter-exchange", "dlx.exchange");
args.put("x-dead-letter-routing-key", "dlx.routing.key");
channel.queueDeclare("normal.queue", true, false, false, args);

应用场景:

  • 订单超时取消:下单后消息进延迟队列,到期进死信队列触发取消逻辑
  • 消息重试失败处理:重试 3 次仍失败,进死信队列人工处理或告警
  • 日志收集:异常消息单独收集分析

【面试题】3. RocketMQ 如何保证顺序消息?全局顺序和分区顺序有什么区别? ​

【标准答案】

RocketMQ 通过以下方式保证顺序:

消息发送端:

  • 使用 MessageQueueSelector,根据业务 key(如订单 ID)选择固定队列
  • 同一订单的消息总是发到同一队列

消息消费端:

  • 使用 MessageListenerOrderly,同一队列的消息由同一线程串行消费
  • 单队列单线程,保证处理顺序

两种顺序级别:

类型说明性能影响
全局顺序所有消息进一个队列,严格按发送顺序消费吞吐量极低
分区顺序相同 key 的消息进同一队列,不同 key 可并行推荐方案

生产环境:分区顺序即可满足 90% 场景,比如订单状态变更(创建→支付→发货→完成),同一订单的顺序性必须保证,不同订单可并行。


【面试题】4. 消息积压了怎么处理?生产环境有没有遇到过? ​

【标准答案】

核心思路:先止血,再治本。

紧急处理(止血):

  1. 扩容消费者:临时增加消费者实例数或线程数
  2. 批量消费:调大 max.poll.records,减少网络开销
  3. 降级非核心业务:暂停非核心消费者,优先处理积压队列

长期方案(治本):

  1. 排查根因:消费者处理慢?数据库瓶颈?下游服务超时?
  2. 优化消费逻辑:异步处理、批量写入、减少 IO
  3. 调整分区数:分区数 >= 消费者数,提升并行度

实战案例: 我们曾遇到订单消息积压 50 万条,根因是数据库慢查询。临时方案:扩容消费者从 5 到 20 台,批量插入数据库。长期方案:优化 SQL,加索引,引入 Redis 缓存热点数据。最终积压 30 分钟清空。


【面试题】5. 如何保证消息消费的幂等性? ​

【标准答案】

幂等性 = 多次消费同一条消息,结果与消费一次一致。

三种主流方案:

  1. 唯一 ID + 数据库唯一索引
sql
-- 消费前插入去重表
INSERT INTO msg_dedup (msg_id, status) VALUES ('123', 'SUCCESS');
-- 唯一索引冲突则跳过
  1. 唯一 ID + Redis 原子操作
java
// SETNX 原子判断
Boolean result = redis.setnx("msg:dedup:" + msgId, "1", 24h);
if (!result) return; // 已消费过
  1. 业务状态机
java
// 订单状态流转,天然幂等
Order order = orderDao.getById(orderId);
if (order.getStatus() >= OrderStatus.PAID) {
    return; // 已支付,跳过
}
orderDao.updateStatus(orderId, OrderStatus.PAID);

推荐:优先使用业务状态机(订单、支付等天然有状态),其次 Redis 去重(高性能),最后数据库去重表(可靠性高但性能一般)。


【面试题】6. Kafka 的 ISR 机制是什么?Leader 挂了怎么选举? ​

【标准答案】

ISR(In-Sync Replicas):与 Leader 保持同步的副本集合,是 Kafka 高可用的核心。

ISR 动态维护:

  • 副本落后超过 replica.lag.time.max.ms(默认 30s)会被踢出 ISR
  • 副本追上 Leader 后重新加入 ISR
  • 只有 ISR 中的副本才能被选为 Leader

Leader 选举流程:

  1. Controller 检测到 Leader 故障(ZooKeeper 或 KRaft 心跳超时)
  2. Controller 从 ISR 中选择第一个存活副本作为新 Leader
  3. 更新 ZooKeeper/KRaft 元数据,通知所有 Broker
  4. Follower 向新 Leader 同步数据

ISR 为空怎么办:

  • unclean.leader.election.enable=false(默认):拒绝选举,等待 ISR 恢复,保证一致性
  • unclean.leader.election.enable=true:允许非 ISR 副本当选,可能丢消息

生产环境推荐:保持默认配置,宁可不可用也不要丢数据。


【面试题】7. RabbitMQ 的四种 Exchange 类型分别适用于什么场景? ​

【标准答案】

Exchange 类型路由规则典型场景
Direct精确匹配 routing key点对点消息、任务分发
Fanout广播到所有绑定队列日志收集、缓存更新通知
Topic模糊匹配 routing key(* 和 #)多条件订阅、消息分类
Headers匹配消息头属性(已废弃)不推荐使用

实战举例:

  1. Direct:用户注册成功后,发送到 user.register 队列,消费者发送欢迎邮件
java
channel.queueBind(queueName, "direct.exchange", "user.register");
  1. Fanout:系统配置更新,广播到所有服务节点刷新本地缓存
java
channel.exchangeDeclare("config.fanout", "fanout");
channel.basicPublish("config.fanout", "", null, msg);
  1. Topic:订单消息按地区和类型订阅,order.beijing.* 匹配北京所有订单类型
java
channel.queueBind(queueName, "order.topic", "order.beijing.*");

【面试题】8. RocketMQ 的事务消息原理是什么?如何保证分布式事务一致性? ​

【标准答案】

核心思想:半消息 + 二次确认 + 回查机制。

完整流程:

  1. 发送半消息:Producer 发送半消息到 Broker,消息暂不可消费(状态为 Prepared)
  2. 执行本地事务:Producer 执行本地数据库事务
  3. 二次确认:
    • 本地事务成功 → 发送 Commit,消息可见
    • 本地事务失败 → 发送 Rollback,消息删除
  4. 回查机制:Producer 宕机未确认,Broker 定时回查 Producer 本地事务状态
  5. 最终决策:根据回查结果 Commit 或 Rollback

代码示例:

java
TransactionListener listener = new TransactionListener() {
    @Override
    public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        // 执行本地事务
        try {
            orderService.createOrder();
            return LocalTransactionState.COMMIT_MESSAGE;
        } catch (Exception e) {
            return LocalTransactionState.ROLLBACK_MESSAGE;
        }
    }
    
    @Override
    public LocalTransactionState checkLocalTransaction(MessageExt msg) {
        // 回查本地事务状态
        boolean success = orderService.checkOrderExists(msg.getKeys());
        return success ? LocalTransactionState.COMMIT_MESSAGE : LocalTransactionState.ROLLBACK_MESSAGE;
    }
};

适用场景:分布式订单创建 + 库存扣减、支付成功 + 积分发放。


【面试题】9. RabbitMQ、Kafka、RocketMQ 怎么选型?你们的架构选型逻辑是什么? ​

【标准答案】

核心对比表:

维度RabbitMQKafkaRocketMQ
吞吐量万级十万级十万级
延迟微秒级毫秒级毫秒级
可靠性高(镜像队列)高(ISR 副本)高(同步刷盘)
功能延迟队列、死信、优先级流处理、Connect事务消息、延迟消息
学习曲线低中中
社区生态成熟最活跃阿里主导

选型逻辑:

  1. 优先 Kafka:日志收集、实时数据流、大数据管道、高吞吐场景(日均百万+)
  2. 优先 RabbitMQ:业务解耦、任务队列、延迟任务、低延迟要求、团队 Java 经验少
  3. 优先 RocketMQ:金融/交易场景、强一致要求、事务消息、阿里云生态

我们团队的选型:

  • 订单系统 → RocketMQ(事务消息保证订单 + 库存一致性)
  • 日志收集 → Kafka(日均 10 亿条日志,高吞吐)
  • 延迟任务 → RabbitMQ(订单超时取消,延迟队列简单)

【面试题】10. Kafka 如何实现 Exactly-Once 语义?生产环境怎么用? ​

【标准答案】

Exactly-Once = 消息不丢失 + 不重复,是 Kafka 0.11+ 引入的核心特性。

三层保障:

  1. 幂等 Producer:单分区去重
properties
enable.idempotence=true
  • Producer 分配 PID(Producer ID)
  • Broker 维护 PID + 序列号,重复消息被去重
  • 仅保证单会话、单分区内幂等
  1. 事务机制:跨分区原子性
java
producer.initTransactions();
producer.beginTransaction();
producer.send(topic1, msg1);
producer.send(topic2, msg2);
producer.commitTransaction(); // 原子提交
  1. 消费者隔离级别:
properties
isolation.level=read_committed
  • 只消费已提交的事务消息
  • 未提交事务消息不对外可见

生产环境配置:

properties
# Producer
enable.idempotence=true
acks=all
transactional.id=order-service-tx-1

# Consumer
enable.auto.commit=false
isolation.level=read_committed

注意点:事务开销大,仅在跨分区一致性要求高的场景使用(如订单 + 库存原子操作),普通场景用幂等 Producer 即可。


题目汇总 ​

本轮覆盖考点:

  • Kafka 消息发送与 ACK 机制
  • RabbitMQ 死信队列
  • RocketMQ 顺序消息
  • 消息积压处理
  • 消息幂等性
  • Kafka ISR 机制与 Leader 选举
  • RabbitMQ Exchange 类型
  • RocketMQ 事务消息
  • 中间件选型对比
  • Kafka Exactly-Once 语义

生成时间:2026-05-21 23:51