延時取消訂單還在用定時任務?來看mq的實現


電商網站中通常會有這樣的需求,訂單創建后,會給用戶兩小時用於支付,如果超時未支付,則要自動取消訂單。最容易想到的實現思路就是用定時任務的方式,每分鍾(或者更短的時間)在數據庫中查詢一次未支付的訂單,檢查距離訂單創建是否超過兩小時,如果超過,則把訂單取消。這種方式在數據庫繁忙時會增加數據庫的壓力,我們可以使用mq更優雅的實現這個需求。

一、 rocketmq的實現

利用rocketmq的延時消息可以很方便的實現這類需求,下面在電商項目中的模擬實現。(這里為了方便測試,把消息的延遲時間設置為1分鍾)。

 

1.寫一個方法,發送一條包含訂單號的消息,設置消息的延遲時間是1分鍾,即消費者1分鍾后才會收到這條消息。

 private void sendDelayMsg(String topic, Long orderId) throws Exception {
        MQEntity entity=new MQEntity();
        entity.setOrderId(orderId);
        Message message=new Message(topic,tag, orderId.toString(), JSON.toJSONString(entity).getBytes());
        //延遲1分鍾發出
        //messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
        message.setDelayTimeLevel(5);
        DefaultMQProducer producer = mqTemplate.getProducer();
        producer.setSendMsgTimeout(20000);
        producer.setVipChannelEnabled(false);
        producer.send(message);
        log.info("發送了一條延時消息,orderId:"+orderId);
    }

 

2.在確認訂單后,調用發送延遲消息的方法,消息的topic是在配置文件中配置的,之后消息監聽器就需要監聽同樣topic的消息。

public Result confirmOrder(TradeOrder order) {
        //1.校驗訂單
        checkOrder(order);
        //2.生成預訂單
        savePreOrder(order);
        try {
            //3.扣減庫存
            reduceGoodsNum(order);
            //4.扣減優惠券
            updateCouponStatus(order.getCouponId(),order.getOrderId());
            //5.使用余額
            reduceMoneyPaid(order);
            //6.確認訂單
            updateOrderStatus(order);
​
            //發送一個延時消息用來定時取消訂單
            sendDelayMsg(delayTopic,order.getOrderId());
            //7.返回成功狀態
            return new Result(ShopCode.SHOP_SUCCESS.getSuccess(),ShopCode.SHOP_SUCCESS.getMessage());
        } catch (Exception e) {
            }
    }

 

3.創建一個消息監聽器,接收上一步發送的延遲消息,取到訂單Id,去數據庫里查詢,判斷訂單是不是尚未支付,如果未支付,則取消訂單。

@Slf4j
@Component
@RocketMQMessageListener(topic = "${mq.delay.topic}",consumerGroup = "${mq.order.consumer.group.name}",
        messageModel = MessageModel.BROADCASTING)
public class DelayMsgListener implements RocketMQListener<MessageExt> {
    @Autowired
    private TradeOrderMapper orderMapper;
    @Override
    public void onMessage(MessageExt messageExt) {
        log.info("接收到延遲消息成功");
        String body=new String(messageExt.getBody());
        MQEntity entity = JSON.parseObject(body, MQEntity.class);
        Long orderId = entity.getOrderId();
        TradeOrder order = orderMapper.selectById(orderId);
        //檢查訂單狀態是否是未支付
        if(!ShopCode.SHOP_ORDER_PAY_STATUS_IS_PAY.equals(order.getPayStatus())){
            order.setOrderStatus(ShopCode.SHOP_ORDER_CANCEL.getCode());
            orderMapper.updateById(order);
            log.info("取消訂單成功,orderId:"+orderId);
        }
​
    }
}

 

4.用單元測試方法,測試訂單的延時取消

@Test
    public void confirmOrder() throws IOException {
        TradeOrder order=new TradeOrder();
        order.setUserId(345963634385633280l);
        order.setCouponId(365959443973935104l);
        order.setCouponPaid(new BigDecimal(50));
        order.setGoodsId(345959443973935104l);
        order.setGoodsNumber(1);
        order.setGoodsPrice(new BigDecimal(5000));
        order.setGoodsAmount(new BigDecimal(5000));
        order.setShippingFee(BigDecimal.ZERO);
        order.setOrderAmount(new BigDecimal(5000));
        order.setMoneyPaid(new BigDecimal(100));
        orderService.confirmOrder(order);
​
        System.in.read();
​
    }

 

5.執行日志如下,可以看到在14:07發送了延遲消息,在14:08分監聽器收到了延遲消息,並且​做了取消訂單的操作。至此,此需求就實現了​。

 

 

二、rabbitmq的實現思路

rabbitmq中沒有延遲消息,但可以為消息設置存活時間,當消息超過了存活時間,則被放到某個死信隊列中,創建一個交換機專門用來處理這個死信隊列中的消息​,就可以實現同樣的功能。

 

三、其它的類似需求

  1. 用戶注冊會員發放的優惠券於一周后失效

  2. 優惠活動持續三天后取消

這種在某個事件發生的一段時間后,要進行的操作,都可以用mq來實現。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM