RabbitMQ 延迟消息应用场景及实现

RabbitMQ 延迟消息应用场景:

  1. 订单指定时间内未支付则自动取消
  2. 用户注册后,三天内未登陆,自动短信提醒
  3. 新入驻店铺,十天内未上传产品,自动短信提醒
  4. 用户发起退货退款,3天内未得到处理则自动通知相关运营人员
  5. 预定会议,会议前10分钟自动提醒各位参会人员
  6. 提醒类场景,如坐火车,坐飞机,接待外来参观人员等等

RabbitMQ实现延时消息有两种方案,第一种是采用rabbitmq-delayed-message-exchange 插件实现,第二种则是利用DLX(Dead Letter Exchanges)+ TTL(消息存活时间)来间接实现。

DLX(Dead Letter Exchanges)+ TTL(消息存活时间)流程图

DLX+ TTL缺点:

消息到了过期时间可能并不会按时“死亡“,因为 RabbitMQ 只会检查第一个消息是否过期,如果过期则丢到死信队列,如果第一个消息的延时时长很长,而第二个消息的延时时长很短,则第二个消息并不会优先得到执行。

延迟插件安装:

RabbitMQ的延迟插件rabbitmq_delayed_message_exchange,此处不详细讲。

实现延迟消息(未支付订单指定时间内自动取消):

一个典型的延迟消息使用场景,待支付订单30分钟不支付(待支付状态),订单就会被取消(取消状态)

<!--消息队列相关依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
  • application.yml添加RabbitMQ的相关配置;

  • spring:
      rabbitmq:
        host: localhost # rabbitmq的连接地址
        port: 5672 # rabbitmq的连接端口号
        virtual-host: /# rabbitmq的虚拟host
        username: admin # rabbitmq的用户名
        password: 123 # rabbitmq的密码
        publisher-confirms: true #如果对异步消息需要回调必须设置为true

     

  • 创建RabbitMQ配置,主要用于配置交换机、队列和绑定关系;

  • /**
     * 定时取消未支付订单消息队列配置
     */
    @Configuration
    public class CancelNonPayOrderRabbitMqConfig {
     
        /**
         * 声明交换机
         */
        @Bean
        CustomExchange  delayedCustomExchange() {
            //创建一个自定义交换机,可以发送延迟消息
            Map<String, Object> args = new HashMap<>();
            args.put("x-delayed-type", "direct");
            return new CustomExchange("order-delayed-exchange", "x-delayed-message",true, false,args);
        }
     
        /**
         * 声明队列
         */
        @Bean
        public Queue delayedQueue() {
            return new Queue("order-delayed-queue");
        }
     
        /**
         * 将队列绑定到交换机
         */
        @Bean
        public Binding delayedQueueBindingDelayedCustomExchange(CustomExchange delayedCustomExchange,Queue delayedQueue) {
            return BindingBuilder
                    .bind(queue)
                    .to(delayedCustomExchange)
                    .with("order-delayed-routing_key")
                    .noargs();
        }
     
    }

     

  • 待支付订单消息生产者,通过给消息设置x-delay头来设置消息从交换机发送到队列的延迟时间;

    /**
     * 订单消息的生产者,用户创建订单时调用sendMessage方法,此时订单状态为待支付
     */
    @Component
    public class SenderOrderMassge {
        private static Logger LOGGER =LoggerFactory.getLogger(OrderMassgeSender.class);
        @Autowired
        private AmqpTemplate amqpTemplate;
     
        /**
         * orderId 订单id
         * delayTimes 延迟时间,单位毫秒
        */
        public void sendMessage(Long orderId,final long delayTimes){
            //发消息
            amqpTemplate.convertAndSend("order-delayed-exchange", "order-delayed-routing_key", orderId, new MessagePostProcessor() {
                @Override
                public Message postProcessMessage(Message message) throws AmqpException {
                    //给消息设置延迟毫秒值
                    message.getMessageProperties().setHeader("x-delay",delayTimes);
                    return message;
                }
            });
            LOGGER.info("send order success, orderId:{}",orderId);
        }
    }

     

  • 取消订单消息的处理者,用于处理订单延迟插件队列中的消息。

    /**
     * 取消订单消息的处理者
     */
    @Component
    @RabbitListener(queues = "order-delayed-queue")
    public class CancelNonPayOrderReceiver {
        private static Logger LOGGER =LoggerFactory.getLogger(CancelNonPayOrderReceiver.class);
        @Autowired
        private OrderService orderService;
        @RabbitHandler
        public void handle(Long orderId){
            LOGGER.info("handle non pay order, orderId:{}",orderId);
            //更改订单状态为"已取消"状态
            orderService.cancelOrder(orderId);
            LOGGER.info("订单已经取消,orderId:{}",orderId); 
        }
    }

     

  • 然后在我们的订单业务实现类中添加如下逻辑,当下单成功之前,往消息队列中发送一个订单的延迟消息,这样如果订单没有被支付的话,就能取消订单了;

    /**
     * 订单管理Service
     */
    @Service
    public class OrderServiceImpl implements OrderService {
        private static Logger LOGGER = LoggerFactory.getLogger(OrderServiceImpl.class);
        @Autowired
        private SenderOrderMassge senderOrderMassge ;
     
        @Override
        public Result addOrder(Order order) {
            //设置订单ID和状态待支付
            long orderId = 10L;//订单ID生成逻辑自行设计
            order.setOrderId(orderId);
            order.setStatus("待支付");
            //此处保存订单信息逻辑
          
            //获取订单超时时间30分钟
            long delayTimes = 1800* 1000;
            //发送延迟消息
            senderOrderMassge.sendMessage(orderId, delayTimes);
            return Result.success(0, "下单成功");
        }
    
        @Override
        public Order findOrder(Long orderId) {
            //通过orderId获取订单信息,具体获取自己实现
            return order;
         
        }
     
     
        @Override
        public void cancelOrder(Long orderId) {
            //获取订单信息,判断订单状态,如果是已支付则不取消订单
            Order order  = findOrder(orderId) 
            if("待支付".equals(order.status)){        
               //取消订单操作       
               order.setOrderId(orderId);
               order.setStatus("已取消");
               //此处保存订单信息逻辑
            }
            LOGGER.info("cancel order success,orderId:{}",orderId);
        }
     
     
    }

    总结

  • 创建订单都会丢消息到延时消息队列,如果设置30分钟过期时间,会有30分钟的冗余,不管已支付,未支付,都会多占30分钟的内存
  • 如果订单已经支付,丢掉此消息即可
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章