消息中间件面试题库(第N轮)
生成时间:2026-05-21 23:51
本轮 消息中间件面试题库(带答案)
【面试题】1. Kafka 的消息发送流程是怎样的?ACK 机制如何保证消息不丢失?
【标准答案】
消息发送流程分三步:
- Producer 发送:Producer 将消息发送到 Broker,消息写入 Leader 副本的 Log 文件
- 副本同步:Follower 副本从 Leader 拉取消息进行同步
- 响应确认: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)是存储"无法被正常消费的消息"的特殊队列。
消息成为死信的三种情况:
- 消息被拒绝(basic.reject/basic_nack)且 requeue=false
- 消息过期(TTL 到期)
- 队列满了,最早的消息被挤出
配置方式:
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. 消息积压了怎么处理?生产环境有没有遇到过?
【标准答案】
核心思路:先止血,再治本。
紧急处理(止血):
- 扩容消费者:临时增加消费者实例数或线程数
- 批量消费:调大 max.poll.records,减少网络开销
- 降级非核心业务:暂停非核心消费者,优先处理积压队列
长期方案(治本):
- 排查根因:消费者处理慢?数据库瓶颈?下游服务超时?
- 优化消费逻辑:异步处理、批量写入、减少 IO
- 调整分区数:分区数 >= 消费者数,提升并行度
实战案例: 我们曾遇到订单消息积压 50 万条,根因是数据库慢查询。临时方案:扩容消费者从 5 到 20 台,批量插入数据库。长期方案:优化 SQL,加索引,引入 Redis 缓存热点数据。最终积压 30 分钟清空。
【面试题】5. 如何保证消息消费的幂等性?
【标准答案】
幂等性 = 多次消费同一条消息,结果与消费一次一致。
三种主流方案:
- 唯一 ID + 数据库唯一索引
-- 消费前插入去重表
INSERT INTO msg_dedup (msg_id, status) VALUES ('123', 'SUCCESS');
-- 唯一索引冲突则跳过- 唯一 ID + Redis 原子操作
// SETNX 原子判断
Boolean result = redis.setnx("msg:dedup:" + msgId, "1", 24h);
if (!result) return; // 已消费过- 业务状态机
// 订单状态流转,天然幂等
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 选举流程:
- Controller 检测到 Leader 故障(ZooKeeper 或 KRaft 心跳超时)
- Controller 从 ISR 中选择第一个存活副本作为新 Leader
- 更新 ZooKeeper/KRaft 元数据,通知所有 Broker
- 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 | 匹配消息头属性(已废弃) | 不推荐使用 |
实战举例:
- Direct:用户注册成功后,发送到
user.register队列,消费者发送欢迎邮件
channel.queueBind(queueName, "direct.exchange", "user.register");- Fanout:系统配置更新,广播到所有服务节点刷新本地缓存
channel.exchangeDeclare("config.fanout", "fanout");
channel.basicPublish("config.fanout", "", null, msg);- Topic:订单消息按地区和类型订阅,
order.beijing.*匹配北京所有订单类型
channel.queueBind(queueName, "order.topic", "order.beijing.*");【面试题】8. RocketMQ 的事务消息原理是什么?如何保证分布式事务一致性?
【标准答案】
核心思想:半消息 + 二次确认 + 回查机制。
完整流程:
- 发送半消息:Producer 发送半消息到 Broker,消息暂不可消费(状态为 Prepared)
- 执行本地事务:Producer 执行本地数据库事务
- 二次确认:
- 本地事务成功 → 发送 Commit,消息可见
- 本地事务失败 → 发送 Rollback,消息删除
- 回查机制:Producer 宕机未确认,Broker 定时回查 Producer 本地事务状态
- 最终决策:根据回查结果 Commit 或 Rollback
代码示例:
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 怎么选型?你们的架构选型逻辑是什么?
【标准答案】
核心对比表:
| 维度 | RabbitMQ | Kafka | RocketMQ |
|---|---|---|---|
| 吞吐量 | 万级 | 十万级 | 十万级 |
| 延迟 | 微秒级 | 毫秒级 | 毫秒级 |
| 可靠性 | 高(镜像队列) | 高(ISR 副本) | 高(同步刷盘) |
| 功能 | 延迟队列、死信、优先级 | 流处理、Connect | 事务消息、延迟消息 |
| 学习曲线 | 低 | 中 | 中 |
| 社区生态 | 成熟 | 最活跃 | 阿里主导 |
选型逻辑:
- 优先 Kafka:日志收集、实时数据流、大数据管道、高吞吐场景(日均百万+)
- 优先 RabbitMQ:业务解耦、任务队列、延迟任务、低延迟要求、团队 Java 经验少
- 优先 RocketMQ:金融/交易场景、强一致要求、事务消息、阿里云生态
我们团队的选型:
- 订单系统 → RocketMQ(事务消息保证订单 + 库存一致性)
- 日志收集 → Kafka(日均 10 亿条日志,高吞吐)
- 延迟任务 → RabbitMQ(订单超时取消,延迟队列简单)
【面试题】10. Kafka 如何实现 Exactly-Once 语义?生产环境怎么用?
【标准答案】
Exactly-Once = 消息不丢失 + 不重复,是 Kafka 0.11+ 引入的核心特性。
三层保障:
- 幂等 Producer:单分区去重
enable.idempotence=true- Producer 分配 PID(Producer ID)
- Broker 维护 PID + 序列号,重复消息被去重
- 仅保证单会话、单分区内幂等
- 事务机制:跨分区原子性
producer.initTransactions();
producer.beginTransaction();
producer.send(topic1, msg1);
producer.send(topic2, msg2);
producer.commitTransaction(); // 原子提交- 消费者隔离级别:
isolation.level=read_committed- 只消费已提交的事务消息
- 未提交事务消息不对外可见
生产环境配置:
# 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