Complete implementation - delay tasks through DelayQueue

There are many ways to implement delayed tasks, and there are many articles about the implementation of delayed tasks on the Internet. For example: 10 ways to implement delayed tasks and so on. However, these articles basically list the methods and give some sample codes. For experienced programmers, they may know how to implement it completely at a glance, but they are not friendly enough for beginners. Therefore, "I plan to write a series of articles, detailing the implementation method, complete implementation code, and working principle of each delayed task. Welcome and look forward to your attention . "

Small concept: what is a delayed task? For example: you bought a train ticket, you must pay within 30 minutes, otherwise the order will be automatically cancelled. "The order will be automatically cancelled without payment within 30 minutes. This task is a delayed task."

1. Application principle of DelayQueue

DelayQueue is an implementation class of an unbounded BlockingQueue that is used to place objects that implement the Delayed interface, in which objects can only be removed from the queue when they expire.

  • BlockingQueue is a blocking queue, a multi-thread-safe queue data structure provided by java. When the number of elements in the queue is 0, the thread trying to get elements from the queue will be blocked or an exception will be thrown.

  • The "unbounded" queue here means that there is no upper limit on the number of elements in the queue, and the capacity of the queue will expand as the number of elements increases.

pictureDelayQueue implements the BlockingQueue interface, so it has the characteristics of unbounded and blocking. In addition, its own core features are:

  • "Delayed task objects put into the queue can only be retrieved after the delay time is reached . "

  • DelayQueue does not receive null elements

  • "DelayQueue only accepts objects that implement the java.util.concurrent.Delayed interface"

Second, the realization of the order delay task

After understanding the characteristics of DelayQueue, we can use it to implement delay tasks and implement java.util.concurrent.Delayedinterfaces.

import org.jetbrains.annotations.NotNull;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

/**
 * 延时订单任务
 */
public class OrderDelayObject implements Delayed {
  private String name;
  private long delayTime;   //延时时间
  //实际业务中这里传订单信息对象,我这里只做demo,所以使用字符串了
  private String order;

  public OrderDelayObject(String name, long delayTime, String order) {
    this.name = name;
    //延时时间加上当前时间
    this.delayTime = System.currentTimeMillis() + delayTime;
    this.order = order;
  }

  //获取延时任务的倒计时时间
  @Override
  public long getDelay(TimeUnit unit) {
    long diff = delayTime - System.currentTimeMillis();
    return unit.convert(diff, TimeUnit.MILLISECONDS);
  }

  //延时任务队列,按照延时时间元素排序,实现Comparable接口
  @Override
  public int compareTo(@NotNull Delayed obj) {
    return Long.compare(this.delayTime, ((OrderDelayObject) obj).delayTime);
  }

  @Override
  public String toString() {
    Date date = new Date(delayTime);
    SimpleDateFormat sd = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    return "\nOrderDelayObject:{"
            + "name=" + name
            + ", time=" + sd.format(date)
            + ", order=" + order
            + "}";
  }
} 

复制代码
  • 上文类中的order为订单信息对象,在实际的业务开发过程中应该是传递订单信息,用于取消订单业务的实现(订单30分钟不付款自动取消)。

  • Delayed接口继承自 Comparable接口,所以需要实现compareTo方法,用于延时任务在队列中按照“延时时间”进行排序。

  • getDelay方法是Delayed接口方法,实现该方法提供获取延时任务的倒计时时间

三、订单处理

首先我们需要一个容器,永久保存延时任务队列,如果是Spring开发环境我们可以这样做。

@Bean("orderDelayQueue")
public DelayQueue<OrderDelayObjectorderDelayQueue(){
    return new DelayQueue<OrderDelayObject>();
}

复制代码

当用户下单的时候,将订单下单任务放入延时队列

@Resource
private DelayQueue<OrderDelayObject> orderDelayQueue;

//发起订单下单的时候将订单演示对象放入orderDelayQueue
orderDelayQueue.add(
        new OrderDelayObject(
                "订单延时取消任务",
                30 * 60 * 1000,    //延时30分钟
                "延时任务订单对象信息"
        )
);

复制代码

系统内开启一个线程,不断的从队列中获取消息,获取到之后对延时消息进行处理。DelayQueue的take方法从队列中获取延时任务对象,如果队列元素数量为0,或者没有到达“延时时间的任务”,该线程会被阻塞。

@Component
public class DelayObjectConsumer  implements InitializingBean {

  @Resource
  private DelayQueue<OrderDelayObject> orderDelayQueue;

  @Override
  public void afterPropertiesSet() throws Exception {
    while (true) {
      OrderDelayObject task = orderDelayQueue.take();
      System.out.println(task.toString());
      System.out.println(task.getOrder());
      //根据order订单信息,去查询该订单的支付信息
      //如果用户没有进行支付,将订单从数据库中关闭
      //如果订单并发量比较大,这里可以考虑异步或线程池的方式进行处理
    }
  }
}

复制代码

需要说明的是,这里的while-true循环的延时任务处理是顺序执行的,在订单并发量比较大的时候,需要考虑异步处理的方式完成订单的关闭操作。我之前写过一个SpringBoot的可观测、易配置的线程池开源项目,可能会对你有帮助,源代码地址:gitee.com/hanxt/zimug…

经过我的测试,放入orderDelayQueue的延时任务,在半小时之后得到正确的执行处理。说明我们的实现是正确的。

四、优缺点

使用DelayQueue实现延时任务非常简单,而且简便,全部都是标准的JDK代码实现,不用引入第三方依赖(不依赖redis实现、消息队列实现等),非常的轻量级。

它的缺点就是所有的操作都是基于应用内存的,一旦出现应用单点故障,可能会造成延时任务数据的丢失。如果订单并发量非常大,因为DelayQueue是无界的,订单量越大,队列内的对象就越多,可能造成OOM的风险。所以使用DelayQueue实现延时任务,只适用于任务量较小的情况。

Code text is not easy, if you find it helpful, please help click to watch or share, I may not be able to persevere without your support! Welcome to the official account: Antetokounmpo, reply 003 to present the PDF version of the author's column "The Way of Docker Cultivation". Antetokounmpo Blog : zimug.com

Guess you like

Origin juejin.im/post/7132774060177981476