完整实现 - 通过 DelayQueue 实现延时任务
一、DelayQueue 的应用原理
二、订单延时任务的实现
三、订单处理
四、优缺点
实现延时任务有很多的方法,网上关于延时任务的实现的文章已经不少了。比如:实现延时任务的 10 种方法等等。但是这些文章基本上都是将方法大概的列举一下,给出部分示例代码,对于有经验的老程序员可能一看就知道该怎么去把它实现完整,但是对于初学者来说不够友好。所以,「我打算写一个系列的文章,详细的给出每种延时任务的实现方法、完整实现代码,以及工作原理,欢迎并期待大家关注我」。
小概念:什么是延时任务?举个例子:你买了一张火车票,必须在 30 分钟之内付款,否则该订单被自动取消。「订单 30 分钟不付款自动取消,这个任务就是一个延时任务。」
一、DelayQueue 的应用原理
DelayQueue 是一个无界的 BlockingQueue 的实现类,用于放置实现了 Delayed 接口的对象,其中的对象只能在其到期时才能从队列中取走。
BlockingQueue 即阻塞队列,java 提供的面向多线程安全的队列数据结构,当队列内元素数量为 0 的时候,试图从队列内获取元素的线程将被阻塞或者抛出异常。
这里的“无界”队列,是指队列的元素数量不存在上限,队列的容量会随着元素数量的增加而扩容。
DelayQueue 实现了 BlockingQueue 接口,所以具有无界、阻塞的特点,除此之外它自己的核心特点就是:
「放入该队列的延时任务对象,只要到达延时时间之后才能被取到」。
DelayQueue 不接收 null 元素
「DelayQueue 只接受那些实现了 java.util.concurrent.Delayed 接口的对象」
二、订单延时任务的实现
了解了 DelayQueue 的特点之后,我们就可以利用它来实现延时任务了,实现java.util.concurrent.Delayed
接口。
上文类中的 order 为订单信息对象,在实际的业务开发过程中应该是传递订单信息,用于取消订单业务的实现(订单 30 分钟不付款自动取消)。
Delayed 接口继承自 Comparable 接口,所以需要实现 compareTo 方法,用于延时任务在队列中按照“延时时间”进行排序。
getDelay 方法是 Delayed 接口方法,实现该方法提供获取延时任务的倒计时时间
三、订单处理
首先我们需要一个容器,永久保存延时任务队列,如果是 Spring 开发环境我们可以这样做。
当用户下单的时候,将订单下单任务放入延时队列
系统内开启一个线程,不断的从队列中获取消息,获取到之后对延时消息进行处理。DelayQueue
的 take 方法从队列中获取延时任务对象,如果队列元素数量为 0,或者没有到达“延时时间的任务”,该线程会被阻塞。
需要说明的是,这里的 while-true 循环的延时任务处理时顺序执行的,在订单并发量比较大的时候,需要考虑异步处理的方式完成订单的关闭操作。我之前写作一个 SpringBoot 的可观测、易配置的线程池开源项目,可能会对你有帮助,源代码地址:https://gitee.com/hanxt/zimug-monitor-threadpool经过我的测试,放入 orderDelayQueue 的延时任务,在半小时之后得到正确的执行处理。说明我们的实现是正确的。
四、优缺点
使用 DelayQueue 实现延时任务非常简单,而且简便,全部都是标准的 JDK 代码实现,不用引入第三方依赖(不依赖 redis 实现、消息队列实现等),非常的轻量级。
它的缺点就是所有的操作都是基于应用内存的,一旦出现应用单点故障,可能会造成延时任务数据的丢失。如果订单并发量非常大,因为 DelayQueue 是无界的,订单量越大,队列内的对象就越多,可能造成 OOM 的风险。所以使用 DelayQueue 实现延时任务,只适用于任务量较小的情况。
更多精彩内容,可以通过我的主页关注我哦,期待您的关注,您的支持是我不竭的创作动力!
版权声明: 本文为 InfoQ 作者【字母哥哥】的原创文章。
原文链接:【http://xie.infoq.cn/article/7b1ea60319562be5cc403d690】。未经作者许可,禁止转载。
评论