订单自动过期时实现方案
具体是因为要做的项目需要做一个过滤过期任务的方案:一开始考虑做定时任务,但发现定时任务并不能完成要求,因为定时任务只能是静态某一时间进行遍历数据库进行修改操作,一般用于清楚数据库缓存等,所以考虑做延时队列。
1. 延时队列
基于JDK的实现方法,将未支付的订单放到一个有序的队列中,程序会自动依次取出过期的订单。
如果当前没有过期的订单,就会阻塞,直至有过期的订单。由于每次只处理过期的订单,并且处理的时间也很精准,不存在定时调度方案的那两个弊端。
实现:
1.首先创建一个订单类OrderDelayDto
需要实现Delayed
接口。然后重写getDelay()
方法和compareTo()
方法,只加了订单编号和过期时间两个属性。
这两个方法很重要,
getDelay()
方法实现过期的策略,比如,订单的过期时间等于当前时间就是过期,返回负数就代表需要处理。否则不处理。
compareTo()
方法实现订单在队列中的排序规则,这样即使后面加入的订单,也能加入到排序中,我这里写的规则是按照过期时间排序,最先过期的排到最前面,这一点很重要,因为排在最前面的如果没有被处理,就会进入阻塞状态,后面的不会被处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| package com.qingyuan.pigeon;
import lombok.Data; import org.jetbrains.annotations.NotNull; import java.util.Date; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit;
@Data public class OrderDelayDto implements Delayed {
private String taskId;
private Date taskEndTime;
@Override public long getDelay(@NotNull TimeUnit unit) { return unit.convert(this.taskEndTime.getTime() - System.currentTimeMillis(), TimeUnit.NANOSECONDS); }
@Override public int compareTo(@NotNull Delayed o) { OrderDelayDto orderDelayDto = (OrderDelayDto) o;
long time =orderDelayDto.getTaskEndTime().getTime(); long time1 = this.taskEndTime.getTime();
return time == time1 ? 0 : time < time1 ? 1 : -1; } }
|
- convert : 将给定单位中的给定持续时间转换为该单位。
- System.currentTimeMillis() : 返回当前时间(毫秒)。
- NANOSECONDS :表示千分之一微秒的时间单位。
写个test 方法测试一下,创建两个订单o1和o2,放入到延时队列中,然后while()方法不断的去取。
在此方法内通过队列的take()
方法获得已过期的订单,然后做出相应的处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| @Test void teat(){ DelayQueue<OrderDelayDto> queue = new DelayQueue<>();
OrderDelayDto o1 = new OrderDelayDto(); o1.setTaskId("1001"); Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.MINUTE,1); o1.setTaskEndTime(calendar.getTime());
OrderDelayDto o2 = new OrderDelayDto(); o2.setTaskId("1002"); o2.setTaskEndTime(new Date());
queue.offer(o1); queue.offer(o2);
while (true) { try { OrderDelayDto take = queue.take(); System.out.println("订单编号:" + take.getTaskId() + "过期时间:" + take.getTaskEndTime()); } catch (InterruptedException e) { e.printStackTrace(); } } }
|
测试结果

即便往队列中放入数据时,先放入o1,先取出的依旧是o2,以此验证队列的排序规则是谁最先过期,无关放入队列的顺序!
2.然而通常情况下,我们会使用多线程去取延时队列中的数据,这样即使线程启动之后也能动态的向队列中添加订单。
创建一个线程类OrderCheckScheduler
实现Runnable
接口,
添加一个延时队列属性,重写run()
方法,在此方法内通过队列的take()
方法获得已过期的订单,然后做出相应的处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import java.util.concurrent.DelayQueue; /** * @author mashu * Date 2020/5/17 14:27 */ public class OrderCheckScheduler implements Runnable {
// 延时队列 private DelayQueue<OrderDelayDto> queue;
public OrderCheckScheduler(DelayQueue<OrderDelayDto> queue) { this.queue = queue; }
@Override public void run() { while (true) { try { OrderDelayDto take = queue.take(); System.out.println("订单编号:" + take.getOrderCode() + " 过期时间:" + take.getExpirationTime()); } catch (InterruptedException e) { e.printStackTrace(); } } } } 1234567891011121314151617181920212223242526
|
好了,写个方法测试一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public static void main(String[] args) { // 创建延时队列 DelayQueue<OrderDelayDto> queue = new DelayQueue<>(); OrderDelayDto o1 = new OrderDelayDto(); //第一个订单,过期时间设置为一分钟后 o1.setOrderCode("1001"); Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.MINUTE, 1); o1.setExpirationTime(calendar.getTime()); OrderDelayDto o2 = new OrderDelayDto(); //第二个订单,过期时间设置为现在 o2.setOrderCode("1002"); o2.setExpirationTime(new Date()); //运行线程 ExecutorService exec = Executors.newFixedThreadPool(1); exec.execute(new OrderCheckScheduler(queue)); //往队列中放入数据 queue.offer(o1); queue.offer(o2); exec.shutdown(); }
|