延时取消订单还在用定时任务?来看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来实现。

posted @ 2021-07-25 14:58  沐一  阅读(1551)  评论(1编辑  收藏  举报