黑马点评中消息队列的RabbitMQ实现

754 阅读3分钟

黑马点评中将消息队列由Redis实现换为RabbitMQ实现

ErlangRabbitMQ的版本兼容问题

作者基于RabbitMQ 4.0.3Erlang 26.2实现。

不同版本的RabbitMQ兼容的Erlang版本也不相同,如果版本不匹配可能会导致报错。具体版本对应关系可查看:Erlang Version Requirements | RabbitMQ

导入pom依赖

 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-amqp</artifactId>
 </dependency>

添加yaml配置文件

 spring:
     rabbitmq:
       host: 127.0.0.1
       port: 5672
       username: guest
       password: guest

编写RabbitConfig文件

 @Bean
 public Queue makeQueue() {
     /**
      * 1、name:    队列名称
      * 2、durable: 是否持久化
             如果为 true,队列会在 RabbitMQ 重启后保持存在,即队列是持久化的。
             如果为 false,队列在 RabbitMQ 重启后会丢失,属于临时队列。
      * 3、exclusive: 是否独享、排外的。
             如果为 true,该队列仅对创建它的连接有效,且其他连接无法访问。
             如果为 false,队列可以被多个连接共享。
      * 4、autoDelete: 是否自动删除。
             如果为 true,当最后一个消费者断开连接时,队列会被自动删除。
             如果为 false,队列即使没有消费者,也不会被自动删除。
      **/
     return new Queue(RabbitConstants.SECKILL_ORDER_QUEUE, true, false, false);
 }
 @Bean
 public DirectExchange makeDirectExchange() {
         /**
      * 1、name:    交换机名称
      * 2、durable: 是否持久化
             如果为 true,交换机会在 RabbitMQ 重启后仍然存在。
             如果为 false,交换机会在 RabbitMQ 重启后丢失。
      * 3、autoDelete: 是否自动删除。
             如果为 true,当没有队列与该交换机绑定时,交换机会自动被删除。
             如果为 false,交换机不会自动删除,即使没有队列与之绑定。
      **/
     return new DirectExchange(RabbitConstants.SECKILL_ORDER_EXCHANGE, true, false);
 }
 @Bean
 public Binding bindDirect() {
     //链式写法,绑定交换机和队列,并设置匹配路由键
     return BindingBuilder
             //绑定队列
             .bind(makeQueue())
             //到交换机
             .to(makeDirectExchange())
             //设置匹配路由键
             .with(RabbitConstants.SECKILL_ORDER_ROUTING_KEY);
 }

编写RabbitConstant文件

 public class RabbitConstants {
     // 队列名称
     public static final String SECKILL_ORDER_QUEUE = "seckill.order.queue";
     // 交换机名称
     public static final String SECKILL_ORDER_EXCHANGE = "seckill.order.exchange";
     // 路由键名称
     public static final String SECKILL_ORDER_ROUTING_KEY = "seckill.order.routing.key";
 }

修改VoucherOrderServiceImpl类中的秒杀逻辑

 @Override
 public Long seckill(Long voucherId) {
     Long userId = UserHolder.getUser().getId();
 ​
     // 1.执行Lua脚本
     Long result = stringRedisTemplate.execute(
             //参数1: 脚本
             SECKILL_SCRIPT,
             //参数2: key集合(已经在seckill脚本中写死,不需要传参)
             Collections.emptyList(),
             //参数3: 参数(优惠券id 和 用户id)
             voucherId.toString(), userId.toString()
     );
     // 2.判断结果
     int r = result.intValue();
     if (r != 0) {
         throw new RuntimeException(r == 1? "库存不足" : "同一用户不可重复下单");
     }
     // 3.将下单信息保存到阻塞队列
     long orderId = redisIdWorker.nextId("order");
     VoucherOrder voucherOrder = new VoucherOrder();
     voucherOrder.setId(orderId);
     voucherOrder.setUserId(userId);
     voucherOrder.setVoucherId(voucherId);
     // 异步下单,三个参数分别为:交换机名称、路由键、传递的数据
     rabbitTemplate.convertAndSend(RabbitConstants.SECKILL_ORDER_EXCHANGE, RabbitConstants.SECKILL_ORDER_ROUTING_KEY, voucherOrder);
 ​
     // 4.返回订单id
     return orderId;
 }

这里有一个需要注意的地方:我们使用的是stringRedisTemplate,所以在执行Lua脚本时需要可变长参数(这里我们传的是优惠券id和用户id)全部转换为String类型(第12行),否则会报错。

编写异步下单工具类(消费者类)

 @Component
 @Slf4j
 @RabbitListener(queues = RabbitConstants.SECKILL_ORDER_QUEUE)
 public class AsyncRabbitListener {
     @Resource
     private IVoucherOrderService voucherOrderService;
     @Resource
     private ISeckillVoucherService seckillVoucherService;
 ​
     // 消息处理方法:异步下单
     @RabbitHandler
     public void AsyncSave(VoucherOrder voucherOrder)
     {
         log.info("接收到存储订单信息的消息:{}", JSONUtil.toJsonStr(voucherOrder));
         boolean success = seckillVoucherService.update().setSql("stock=stock-1").eq("voucher_id", voucherOrder.getVoucherId()).gt("stock", 0).update();
         voucherOrderService.save(voucherOrder);
         log.info("订单信息存储完成:{}",success);
     }
 }

对于单方法消费者类:可以直接使用@RabbitListener来指定某个方法作为消息监听器,这个方法会接收来自指定队列的消息。常用参数为queues,用于指定监听的队列名,可以是单个队列或多个队列。

对于多方法消费者类:可以在类上使用@RabbitListener来指定某个类作为消息监听器,在类内使用@RabbitHandler来区分不同的消息处理方法。举例如下:

 @RabbitListener(queues = "myQueue")
 public class MyQueueListener {
 ​
     @RabbitHandler
     public void handleStringMessage(String message) {
         System.out.println("Received String message: " + message);
     }
 ​
     @RabbitHandler
     public void handleOrderMessage(Order order) {
         System.out.println("Received Order message: " + order);
     }
 }