RabbitMQ业务场景面试题
以下是几道 RabbitMQ 与 Java 业务场景结合的面试题及其答案解析,涵盖核心概念、实战设计和常见问题处理:
1. 基础题:RabbitMQ 的消息持久化机制
题目:
在 Java 项目中,如何确保 RabbitMQ 的消息在服务器重启后不丢失?请结合代码说明关键配置。
答案:
消息持久化需要同时配置 队列持久化 和 消息持久化:
// 队列持久化(durable=true)
channel.queueDeclare("myQueue", true, false, false, null);
// 发送持久化消息(deliveryMode=2)
AMQP.BasicProperties props = new AMQP.BasicProperties.Builder()
.deliveryMode(2) // 持久化消息
.build();
channel.basicPublish("myExchange", "myRoutingKey", props, message.getBytes());
关键点:
- 队列持久化(
durable=true
):确保队列元数据在服务器重启后保留。 - 消息持久化(
deliveryMode=2
):将消息写入磁盘。 - 注意:仅配置消息持久化而不持久化队列,队列丢失后消息也无法恢复。
2. 设计题:订单超时取消场景
题目:
如何用 RabbitMQ 实现“30分钟未支付的订单自动取消”功能?请说明设计思路和 RabbitMQ 特性。
答案:
方案一:TTL + 死信队列(DLX)
- 订单创建时,发送消息到
order.create
队列,设置 TTL=30分钟。 - 消息过期后,通过死信交换机(DLX)路由到
order.cancel
队列。 - 消费者监听
order.cancel
队列,执行取消逻辑。
Java 代码示例:
// 创建死信交换机和队列
channel.exchangeDeclare("dlx", "direct");
channel.queueDeclare("order.cancel", true, false, false, null);
channel.queueBind("order.cancel", "dlx", "order.cancel");
// 原始队列绑定死信交换机
Map<String, Object> args = new HashMap<>();
args.put("x-dead-letter-exchange", "dlx");
args.put("x-dead-letter-routing-key", "order.cancel");
channel.queueDeclare("order.create", true, false, false, args);
// 发送消息并设置 TTL
AMQP.BasicProperties props = new AMQP.BasicProperties.Builder()
.expiration("1800000") // 30分钟(单位毫秒)
.build();
channel.basicPublish("", "order.create", props, orderId.getBytes());
方案二:延迟队列插件(rabbitmq-delayed-message-exchange)
使用 RabbitMQ 官方插件直接支持延迟消息,但需确保插件已安装。
关键点:
- TTL+DLX 是通用方案,延迟队列插件更简洁但依赖环境配置。
- 避免消息阻塞:若队列头部的消息未过期,后续消息即使过期也不会被处理。
3. 陷阱题:消息重复消费问题
题目:
消费者处理消息时可能因异常导致消息未正确 ACK,引发消息重复消费。如何在 Java 中避免重复处理?
答案:
方案一:幂等性设计
- 在业务逻辑中通过唯一标识(如订单ID)判断是否已处理,例如:
// 伪代码:检查订单是否已处理
if (!orderService.isProcessed(orderId)) {
processOrder(orderId);
}
方案二:数据库唯一约束
- 插入处理记录时,利用数据库唯一索引(如
order_id
)防止重复提交。
方案三:Redis 分布式锁
- 在处理前获取锁:
String lockKey = "order_lock:" + orderId;
if (redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS)) {
try {
processOrder(orderId);
} finally {
redisTemplate.delete(lockKey);
}
}
关键点:
- RabbitMQ 本身不保证消息仅消费一次,需业务端实现幂等。
- 消息重试时需合理设置
requeue
策略,避免无限循环。
4. 实战题:Topic Exchange 的路由规则
题目:
假设有一个日志系统,需将 error
级别的日志存储到数据库,其他日志写入文件。如何设计 Exchange 和 Binding Key?
答案:
设计步骤:
- 使用 Topic Exchange,定义两个队列:
db.logs.queue
:绑定 Binding Keylogs.error
。file.logs.queue
:绑定 Binding Keylogs.*
(匹配所有级别)。
- 生产者根据日志级别发送消息,例如:
- Routing Key
logs.error
发送到数据库队列。 - Routing Key
logs.info
、logs.warn
等发送到文件队列。
- Routing Key
Java 代码示例:
// 绑定队列到 Topic Exchange
channel.queueBind("db.logs.queue", "logs.exchange", "logs.error");
channel.queueBind("file.logs.queue", "logs.exchange", "logs.*");
// 发送 error 日志到数据库队列
channel.basicPublish("logs.exchange", "logs.error", null, "ERROR: ...".getBytes());
// 发送 info 日志到文件队列
channel.basicPublish("logs.exchange", "logs.info", null, "INFO: ...".getBytes());
关键点:
logs.*
匹配一个单词(如error
、info
),logs.#
可匹配多级路径。- 数据库队列需单独处理,避免被
logs.*
匹配到。
5. 高级题:集群与高可用设计
题目:
RabbitMQ 集群如何实现高可用?Java 客户端连接集群时需要注意什么?
答案:
高可用方案:
- 镜像队列(Mirrored Queues):
- 队列镜像到多个节点,主节点故障时自动切换。
- 配置策略:
rabbitmqctl set_policy ha-all "^ha." '{"ha-mode":"all"}'
- 客户端负载均衡:
- Java 客户端连接多个节点地址,避免单点故障:
ConnectionFactory factory = new ConnectionFactory(); factory.setHosts("node1:5672,node2:5672,node3:5672"); factory.setUsername("guest"); factory.setPassword("guest");
- Java 客户端连接多个节点地址,避免单点故障:
Java 客户端注意事项:
- 启用自动重连:
factory.setAutomaticRecoveryEnabled(true); // 自动恢复连接 factory.setNetworkRecoveryInterval(5000); // 重试间隔
- 避免消息丢失:在生产者端启用
Confirm模式
,确保消息成功到达 Broker。
总结
以上问题覆盖了 RabbitMQ 的核心概念、Java 集成、实际场景设计和常见陷阱。面试时可结合候选人的经验层次,选择基础题考察概念理解,或通过设计题考察实战能力。