電商網站中通常會有這樣的需求,訂單創建后,會給用戶兩小時用於支付,如果超時未支付,則要自動取消訂單。最容易想到的實現思路就是用定時任務的方式,每分鍾(或者更短的時間)在數據庫中查詢一次未支付的訂單,檢查距離訂單創建是否超過兩小時,如果超過,則把訂單取消。這種方式在數據庫繁忙時會增加數據庫的壓力,我們可以使用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中沒有延遲消息,但可以為消息設置存活時間,當消息超過了存活時間,則被放到某個死信隊列中,創建一個交換機專門用來處理這個死信隊列中的消息,就可以實現同樣的功能。
三、其它的類似需求
-
用戶注冊會員發放的優惠券於一周后失效
-
優惠活動持續三天后取消
這種在某個事件發生的一段時間后,要進行的操作,都可以用mq來實現。