我正在参加「掘金·启航计划」
简介:
DelayQueue是BlockingQueue的一种,所以它是线程安全的,DelayQueue的特点就是插入Queue中的数据可以按照自定义的delay时间进行排序。只有delay时间小于0的元素才能够被取出。
先看一下DelayQueue的定义:
public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
implements BlockingQueue<E>
从定义可以看到,DelayQueue中存入的对象都必须是Delayed的子类。
Delayed继承自Comparable,并且需要实现一个getDelay的方法。
DelayQueue的应用:
DelayQueue一般用于生产者消费者模式,我们下面举一个取消订单的具体的例子。
首先要使用DelayQueue,必须自定义一个Delayed对象:
@Data
@TableName(value = "tb_order")
public class Order extends Model<Order> implements Delayed{
/**
* 订单号
*/
@TableId(value = "order_no")
private String orderNo;
/**
* 用户id
*/
@TableField(value = "user_id")
private String userId;
/**
* 订单状态(0待支付,1已支付,2已取消)
*/
@TableField(value = "status")
private Integer status;
/**
* 订单创建时间
*/
@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
@TableField(value = "create_time")
private Date createTime;
/**
* 订单取消时间
*/
@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
@TableField(value = "cancel_time")
private Date cancelTime;
public Order() {
}
public Order(String orderNo, String userId, Integer status, Date createTime, Date cancelTime) {
this.orderNo = orderNo;
this.userId = userId;
this.status = status;
this.createTime = createTime;
this.cancelTime = cancelTime;
}
@Override
public long getDelay(TimeUnit unit) {
//下面用到unit.convert()方法,其实在这个小场景不需要用到,只是学习一下如何使用罢了
return unit.convert(cancelTime.getTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(Delayed o) {
//这里根据取消时间来比较,如果取消时间小的,就会优先被队列提取出来
return this.getCancelTime().compareTo(((Order) o).getCancelTime());
}
}
因为DelayQueue的数据是保存在内存中的可能会丢失,这里创建了一个TrOrder的对象用了持久化队列的数据到数据库
@Data
@TableName(value = "tr_order")
public class TrOrder extends Model<TrOrder> {
/**
* 订单号
*/
@TableId(value = "order_no")
private String orderNo;
/**
* 用户id
*/
@TableField(value = "user_id")
private String userId;
/**
* 订单状态(0待支付,1已支付,2已取消)
*/
@TableField(value = "status")
private Integer status;
/**
* 订单创建时间
*/
@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
@TableField(value = "create_time")
private Date createTime;
/**
* 订单取消时间
*/
@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
@TableField(value = "cancel_time")
private Date cancelTime;
}
创建一个取消订单的接口,编写具体实现:
public interface CancelOrderService {
/**
* 取消订单
*/
void cancelOrder();
/**
* 获取队列
* @return
*/
DelayQueue<Order> getOrder();
void updateQueue(String id);
}
实现类:
@Service
@Slf4j
public class CancelOrderServiceImpl implements CancelOrderService {
/**
* 是否开启自动取消功能
*/
@Value("${order.isStarted}")
private int isStarted;
/**
* 延迟队列,用来存放订单对象
*/
DelayQueue<Order> queue = new DelayQueue<>();
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String key = "delay:order";
@Resource
private ThreadPoolTaskExecutor executorService;
@Override
public void cancelOrder() {
//新建一个线程,用来模拟定时取消订单job
executorService.submit(()->{
try {
log.info("开启自动取消订单job,当前时间:" + DateUtil.date());
List<Order> orderList = new ArrayList<>();
while (true) {
String s = (String) redisTemplate.opsForList().rightPop(key);
Order order = JSON.parseObject(s, Order.class);
if (order != null) {
orderList.add(order);
} else {
break;
}
}
if (CollUtil.isNotEmpty(orderList)) {
merge(orderList);
}
while (isStarted == 1) {
try {
Order order = queue.take();
log.info("json:{}",JSON.toJSONString(order));
redisTemplate.opsForList().remove(key,0,JSON.toJSONString(order));
order.setStatus(2);
order.updateById();
log.info("订单:" + order.getOrderNo() + "付款超时,自动取消,当前时间:" + DateUtil.date());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
}
});
}
@Override
public DelayQueue<Order> getOrder() {
executorService.submit(()->{
try {
Date date = DateUtil.date();
for (int i = 1; i < 6; i++) {
Order order = new Order("SO00"+i, "00"+i, 0,date, DateUtil.offset(date, DateField.MINUTE, 1+i));
order.insert();
Order order1 = order.selectById();
queue.add(order1);
// log.info("json:{}",JSON.toJSONString(order));
redisTemplate.opsForList().leftPush(key, JSON.toJSONString(order1));
//设置过期时间
redisTemplate.expire(key,48, TimeUnit.HOURS);
log.info("redis已保存");
}
} catch (Exception e) {
e.printStackTrace();
}
});
return queue;
}
@Override
public void updateQueue(String id) {
Order order = new Order().selectOne(new QueryWrapper<Order>().eq("order_no", id));
if (order != null) {
// log.info("json:{}",JSON.toJSONString(order));
redisTemplate.opsForList().remove(key,0,JSON.toJSONString(order));
remove(id);
order.setCancelTime(DateUtil.offset(DateUtil.date(), DateField.MINUTE, 1));
order.updateById();
Order order1 = order.selectById();
boolean add = queue.add(order1);
redisTemplate.opsForList().leftPush(key, JSON.toJSONString(order1));
log.info(add+"");
}
}
public void remove(String id){
Order[] array = queue.toArray(new Order[]{});
if(array == null || array.length <= 0){
return;
}
Order target = null;
for(Order order : array){
if(order.getOrderNo().equals(id)){
target = order;
break;
}
}
if(target != null){
boolean remove = queue.remove(target);
log.info(remove+"");
}
}
public void merge(List<Order> list){
log.info("进入redis合并队列");
Order[] array = queue.toArray(new Order[]{});
if(array == null || array.length <= 0){
log.info("队列为空");
for (Order order : list) {
queue.add(order);
}
}
List<String> ids = new ArrayList<>();
Order target = null;
for(Order order : array){
if (!ids.contains(order.getOrderNo())) {
ids.add(order.getOrderNo());
}
}
if (CollUtil.isNotEmpty(ids)) {
log.info("队列不为空");
for (Order order : list) {
if (!ids.contains(order.getOrderNo())) {
queue.add(order);
}
}
}
}
public static void main(String[] args) {
DateTime date = DateUtil.date();
System.out.println(date);
DateTime offset = DateUtil.offset(date, DateField.MINUTE, 1);
System.out.println(offset);
}
}
这里编写取消订单的方法
@Override
public void cancelOrder() {
//新建一个线程,用来模拟定时取消订单job
executorService.submit(()->{
try {
log.info("开启自动取消订单job,当前时间:" + DateUtil.date());
List<Order> orderList = new ArrayList<>();
while (true) {
String s = (String) redisTemplate.opsForList().rightPop(key);
Order order = JSON.parseObject(s, Order.class);
if (order != null) {
orderList.add(order);
} else {
break;
}
}
if (CollUtil.isNotEmpty(orderList)) {
merge(orderList);
}
while (isStarted == 1) {
try {
Order order = queue.take();
log.info("json:{}",JSON.toJSONString(order));
redisTemplate.opsForList().remove(key,0,JSON.toJSONString(order));
order.setStatus(2);
order.updateById();
log.info("订单:" + order.getOrderNo() + "付款超时,自动取消,当前时间:" + DateUtil.date());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
}
});
}
public void merge(List<Order> list){
log.info("进入redis合并队列");
Order[] array = queue.toArray(new Order[]{});
if(array == null || array.length <= 0){
log.info("队列为空");
for (Order order : list) {
queue.add(order);
}
}
List<String> ids = new ArrayList<>();
Order target = null;
for(Order order : array){
if (!ids.contains(order.getOrderNo())) {
ids.add(order.getOrderNo());
}
}
if (CollUtil.isNotEmpty(ids)) {
log.info("队列不为空");
for (Order order : list) {
if (!ids.contains(order.getOrderNo())) {
queue.add(order);
}
}
}
}
这里就是从项目启动后从数据库中读取有没有缓存的数据,有的话和队列合并,然后由队列取出数据修改订单状态,并且删除对应的缓存数据
这里创建一个模拟创建订单数据的方法
@Override
public DelayQueue<Order> getOrder() {
executorService.submit(()->{
try {
Date date = DateUtil.date();
for (int i = 1; i < 6; i++) {
Order order = new Order("SO00"+i, "00"+i, 0,date, DateUtil.offset(date, DateField.MINUTE, 1+i));
order.insert();
Order order1 = order.selectById();
queue.add(order1);
// log.info("json:{}",JSON.toJSONString(order));
redisTemplate.opsForList().leftPush(key, JSON.toJSONString(order1));
//设置过期时间
redisTemplate.expire(key,48, TimeUnit.HOURS);
log.info("redis已保存");
}
} catch (Exception e) {
e.printStackTrace();
}
});
return queue;
}
然后要想让我们的任务在程序启动后执行需要任务实现CommandLineRunner接口
@Component
@Order(-1)
public class StartComponent implements CommandLineRunner {
@Autowired
private CancelOrderService cancelOrderService;
@Autowired
private CancelOrderMysqlService cancelOrderMysqlService;
@Override
public void run(String... args) throws Exception {
cancelOrderMysqlService.cancelOrder();
}
}
然后启动添加5条数据看下执行效果:
可以看到订单到取消时间后会自定执行取消订单的任务.