Bootstrap

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)

  1. 订单创建时,发送消息到 order.create 队列,设置 TTL=30分钟。
  2. 消息过期后,通过死信交换机(DLX)路由到 order.cancel 队列。
  3. 消费者监听 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?

答案
设计步骤

  1. 使用 Topic Exchange,定义两个队列:
    • db.logs.queue:绑定 Binding Key logs.error
    • file.logs.queue:绑定 Binding Key logs.*(匹配所有级别)。
  2. 生产者根据日志级别发送消息,例如:
    • Routing Key logs.error 发送到数据库队列。
    • Routing Key logs.infologs.warn 等发送到文件队列。

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.* 匹配一个单词(如 errorinfo),logs.# 可匹配多级路径。
  • 数据库队列需单独处理,避免被 logs.* 匹配到。

5. 高级题:集群与高可用设计

题目
RabbitMQ 集群如何实现高可用?Java 客户端连接集群时需要注意什么?

答案
高可用方案

  1. 镜像队列(Mirrored Queues)
    • 队列镜像到多个节点,主节点故障时自动切换。
    • 配置策略:
      rabbitmqctl set_policy ha-all "^ha." '{"ha-mode":"all"}'
      
  2. 客户端负载均衡
    • Java 客户端连接多个节点地址,避免单点故障:
      ConnectionFactory factory = new ConnectionFactory();
      factory.setHosts("node1:5672,node2:5672,node3:5672");
      factory.setUsername("guest");
      factory.setPassword("guest");
      

Java 客户端注意事项

  • 启用自动重连:
    factory.setAutomaticRecoveryEnabled(true); // 自动恢复连接
    factory.setNetworkRecoveryInterval(5000);  // 重试间隔
    
  • 避免消息丢失:在生产者端启用 Confirm模式,确保消息成功到达 Broker。

总结

以上问题覆盖了 RabbitMQ 的核心概念、Java 集成、实际场景设计和常见陷阱。面试时可结合候选人的经验层次,选择基础题考察概念理解,或通过设计题考察实战能力。

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;