延时取消订单还在用定时任务?来看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中没有延迟消息,但可以为消息设置存活时间,当消息超过了存活时间,则被放到某个死信队列中,创建一个交换机专门用来处理这个死信队列中的消息,就可以实现同样的功能。
三、其它的类似需求
-
用户注册会员发放的优惠券于一周后失效
-
优惠活动持续三天后取消
这种在某个事件发生的一段时间后,要进行的操作,都可以用mq来实现。