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分鐘的內存
  • 如果訂單已經支付,丟掉此消息即可
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章