取消超时订单
- 使用场景
- 方案
- 优化
1.使用场景
12306订单30分钟自动取消?
淘宝订单超过2小时自动取消?
美团外卖订单超过30分钟自动取消?
抢购如何处理?被动更新 + crond 主动更新两种方式,因为是抢购,下单扣库存,5分钟不支付马上过期恢复库存。
订单支付的时候再去校验时间是否过期,查询校验一次、订单支付校验一次
另请注意,请判断好支付完成回调的验证,因为用户下单后,20几分钟后再点击付款,再到支付页面停留,时间已经超过30分钟,然后支付成功回调时请注意判断验证回调的信息
2.方案
被动过期策略
方案1:
在每次查询这个订单时候检查过期,被动过期。
比如,查询订单细节时,再去检查是否过期然后再处理。
当然,如果这条数据不被访问可能永远不会过期,直到有人访问它。
有点像薛定谔的猫,在你打开盒子(检查订单)之后,才知道它是否过期。所以叫被动过期。
方案2:redis
在创建订单时候,将订单id作为key存入redis,过期时间为30分钟。
用户查询待支付订单列表时,检查id是否还存于redis,不存在则关闭订单。发起支付时同理关闭订单,订单支付成功回调时则将订单置为退款状态。
主动过期策略
方案3:数据库轮训、定时任务:springboot的自带的schedule的注解、用quartz,定时器
写个定时任务,每一分钟查询未支付订单,如果其时间超过大于等于创建订单时间30分钟,则关闭订单
优点:实现简单、无技术难点、异常恢复、支持分布式/集群环境
缺点:性能低
方案4:java延迟队列delayQueue
优点:实现简单、性能较好
缺点:异常恢复困难、分布式/集群实现困难
1.知识点
delayQueue相关api:DelayQueue是java.util.concurrent中提供的一个类。
DelayQueue小结
DelayQueue是一个有序的无界BlockingQueue,用于放置实现了Delayed接口的对象,其中的对象在到期时才能从队列中取走。
DelayQueue只能添加实现了Delayed接口的对象,不能将null元素放置到这种队列中。
BlockingQueue中add,offer,put方法区别
- add:将指定的元素插入到此队列中,在成功时返回 true,如果当前没有可用空间,则抛出 IllegalStateException,该方式为非阻塞添加。
- offer:将指定元素插入到此队列的尾部(如果立即可行且不会超出此队列的容量),在成功时返回 true,如果此队列已满,则返回 false,此方法通常要优于 add 方法,该方式为非阻塞添加。
- put:将指定元素插入到此队列的尾部,该方式为阻塞添加,则等待空间变得可用。
- take:出队,将符合条件的元素取出,take()方法阻塞等待,有过期元素时继续。
线程池知识点
SpringBoot中CommandLineRunner和ApplicationRunner接口解析和使用
2.步骤
2.1 创建需要延迟处理的类实现接口Delayed,重写getDelay和compareTo方法,如延迟订单类
import java.util.concurrent.DelayQueue; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; public class OrderDelay implements Delayed{ private Long orderId; private Long createTime; // 创建时间 public OrderDelay(Long orderId,Long createTime){ this.orderId = orderId; this.createTime = createTime; } // 判断对象是否过期(小于等于0时过期) @Override public long getDelay(TimeUnit unit) { return createTime+24*60*60*1000l-System.currentTimeMillis(); // 创建订单24小时未支付即过期 } // 根据对象过期时间排序, 哪个元素最早到过期时间, 就排在前面 @Override public int compareTo(Delayed delayed) { OrderDelay that = (OrderDelay)delayed; if(this.createTime>that.createTime){ return 1; }else if(this.createTime==that.createTime){ return 0; }else { return -1; } } public Long getOrderId() { return orderId; } public Long getCreateTime() { return createTime; } }
2.2 定义一个Job类,实现CommandLineRunner接口,用于在项目启动后执行该任务,通过take()方法从队列中获取到指定对象出列
2.3 在创建订单时,将该订单放入延迟队列中
Order order = new Order(); order.setOrderNumber(String.valueOf(now.toInstant().toEpochMilli())); order.setUserId(userId); order.setSubjectName(productName); order.setProductType(productType); order.setPayAmount(payAmount); order.setYears(years); order.setOrderStatus(0); order.setPayType(1); order.setCreateTime(now); order.setIsDelete(0); orderMapper.addOrder(order); OrderDelay orderDelay = new OrderDelay(order.getId(), order.getCreateTime().toInstant().toEpochMilli()); OrderDelayQueueJob.delayQueue.add(orderDelay); // 将延迟订单放入延迟队列中
2.4当客户取消订单的时候,从Queue里面删除订单信息
方案5:消息队列:active
使用RabbitMq 实现 RabbitMq实现延迟队列
优点: 开源,现成的稳定的实现方案;
缺点: RabbitMq是一个消息中间件;延迟队列只是其中一个小功能,如果团队技术栈中本来就是使用RabbitMq那还好,如果不是,那为了使用延迟队列而去部署一套RabbitMq成本有点大;