java环形队列定时任务_延时任务队列的原理与实现总结

延时任务有别于定式任务,定式任务往往是固定周期的,有明确的触发时间。而延时任务一般没有固定的开始时间,它常常是由一个事件触发的,而在这个事件触发之后的一段时间内触发另一个事件。也就是说,任务事件生成时并不想让消费者立即拿到,而是延迟一定时间后才接收到该事件进行消费。

延迟任务相关的业务场景如下:

场景一:在订单系统中,一个用户某个时刻下单之后通常有30分钟的时间进行支付,如果30分钟之内没有支付成功,那么这个订单将自动进行过期处理。

场景二:用户某个时刻通过手机远程遥控家里的智能设备在指定的时间进行工作。这时就可以将用户指令发送到延时队列,当指令设定的时间到了再将指令推送到只能设备。

下面我们来探讨一些方案,其实这些方案没有好坏之分,和系统架构一样,只有最适合。对于数据量较小的情况下,任意一种方案都可行,考虑的是简单明了和开发速度,尽量避免把系统搞复杂了。而对于数据量较大的情况下,就需要有一些选择,并不是所有的方案都适合了。

解决方式

定时器轮询遍历数据库记录

JDK的DelayQueue

JDK ScheduledExecutorService

时间轮(netty)

利用quartz等定时任务

Redis的ZSet实现

rabbitMq实现延时队列

定时器轮询遍历数据库记录

这是比较常见的一种方式,所有的订单或者所有的命令一般都会存储在数据库中。我们会起一个线程定时去扫数据库或者一个数据库定时Job,找到那些超时的数据,直接更新状态,或者拿出来执行一些操作。这种方式很简单,不会引入其他的技术,开发周期短。

如果数据量比较大,千万级甚至更多,插入频率很高的话,上面的方式在性能上会出现一些问题,查找和更新对会占用很多时间,轮询频率高的话甚至会影响数据入库。一种可以尝试的方式就是使用类似TBSchedule或Elastic-Job这样的分布式的任务调度加上数据分片功能,把需要判断的数据分到不同的机器上执行。

如果数据量进一步增大,那扫数据库肯定就不行了。另一方面,对于订单这类数据,我们也许会遇到分库分表,那上述方案就会变得过于复杂,得不偿失。

JDK的DelayQueue

Java中的DelayQueue位于java.util.concurrent包下,作为单机实现,它很好的实现了延迟一段时间后触发事件的需求。由于是线程安全的它可以有多个消费者和多个生产者,从而在某些情况下可以提升性能。DelayQueue本质是封装了一个PriorityQueue,使之线程安全,加上Delay功能,也就是说,消费者线程只能在队列中的消息“过期”之后才能返回数据获取到消息,不然只能获取到null。

之所以要用到PriorityQueue,主要是需要排序。也许后插入的消息需要比队列中的其他消息提前触发,那么这个后插入的消息就需要最先被消费者获取,这就需要排序功能。PriorityQueue内部使用最小堆来实现排序队列。队首的,最先被消费者拿到的就是最小的那个。使用最小堆让队列在数据量较大的时候比较有优势。使用最小堆来实现优先级队列主要是因为最小堆在插入和获取时,时间复杂度相对都比较好,都是O(logN)。

下面例子实现了未来某个时间要触发的消息。我把这些消息放在DelayQueue中,当消息的触发时间到,消费者就能拿到消息,并且消费,实现处理方法。示例代码:

/*

* 定义放在延迟队列中的对象,需要实现Delayed接口

*/

public class DelayedTask implements Delayed {

private int _expireInSecond = 0;

public DelayedTask(int delaySecond) {

Calendar cal = Calendar.getInstance();

cal.add(Calendar.SECOND, delaySecond);

_expireInSecond = (int) (cal.getTimeInMillis() / 1000);

}

public int compareTo(Delayed o) {

long d = (getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS));

re

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
延时队列定时任务的区别在于,延时队列是按照元素添加的时间来进行排序,而定时任务是按照预定的时间来触发执行的。延时队列可以用于实现某些需要延迟处理的业务场景,例如延迟发布、延迟重试等场景,而定时任务则适用于需要周期性执行任务的场景。 下面是使用Java实现延时队列的示例代码: ``` import java.util.concurrent.DelayQueue; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; public class DelayQueueDemo { public static void main(String[] args) { DelayQueue<DelayedElement> delayQueue = new DelayQueue<>(); delayQueue.offer(new DelayedElement("A", 3000)); delayQueue.offer(new DelayedElement("B", 2000)); delayQueue.offer(new DelayedElement("C", 1000)); while (!delayQueue.isEmpty()) { try { DelayedElement element = delayQueue.take(); System.out.println(element.getData() + "被取出,时间为:" + System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } } } } class DelayedElement implements Delayed { private String data; private long expireTime; public DelayedElement(String data, long delayTime) { this.data = data; this.expireTime = System.currentTimeMillis() + delayTime; } public String getData() { return data; } @Override public long getDelay(TimeUnit unit) { return unit.convert(expireTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS); } @Override public int compareTo(Delayed o) { return Long.compare(this.getDelay(TimeUnit.MILLISECONDS), o.getDelay(TimeUnit.MILLISECONDS)); } } ``` 在这个示例中,我们定义了一个`DelayedElement`类,它实现了`Delayed`接口,并重写了`getDelay`和`compareTo`方法。`getDelay`方法返回该元素离过期时间还有多少时间,`compareTo`方法用于比较两个元素的顺序。 在`main`方法中,我们首先创建了一个`DelayQueue`对象,并向其中添加了三个元素,分别延时1秒、2秒和3秒。然后我们不断从队列中取出元素,直到队列为空。在取出元素的过程中,由于是按照元素到期时间来排序的,所以最先到期的元素会被最先取出。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值