背景

最近项目中有个业务,需要对用户新增任务到期后进行业务处理。使用定时任务定时扫描过期时间,浪费资源,且不实时。只能使用延时队列处理。

DelayQueue

第一想到的是java自带的延时队列delayqueue。

首先实现一个Delyed类。

实现两个最重要方法。第一个是队列里面的消息排序。DelayQueue底层使用的是阻塞队列。队列的消费端会去take队列的头部元素,没有元素就阻塞在那里。因此,延迟队列中的元素必须按执行时间顺序排列。

@Override
public int compareTo(Delayed delayed) {
Message message = (Message) delayed;
return this.exceptTime > message.getExceptTime() ? 1 : 0;
}

第二个方法是剩余时间延迟时间。每加入一个元素时将延迟时间传入,得到一个预期执行时间。每当执行此方法的时候,使用预期时间减去当前时间,即时剩余延迟时间。换句话说,还有多长时间执行。为0时立即执行。

@Override
public long getDelay(TimeUnit unit) {
System.out.println(exceptTime - System.nanoTime());
return unit.convert(exceptTime - System.nanoTime(), TimeUnit.SECONDS);
}

全部代码:

import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit; public class Message implements Delayed{ private Integer id;
private String content;
private long delay;//延迟时间
private long exceptTime;//执行时间 public Message() {} public Message(Integer id, String content, long delay) {
this.id = id;
this.content = content;
this.delay = delay;
this.exceptTime = System.nanoTime() + delay;
} @Override
public int compareTo(Delayed delayed) {
Message message = (Message) delayed;
return this.exceptTime > message.getExceptTime() ? 1 : 0;
} @Override
public long getDelay(TimeUnit unit) {
System.out.println(exceptTime - System.nanoTime());
return unit.convert(exceptTime - System.nanoTime(), TimeUnit.SECONDS);
} public String getContent() {
return content;
} public void setContent(String content) {
this.content = content;
} public Integer getId() {
return id;
} public void setId(Integer id) {
this.id = id;
} public long getDelay() {
return delay;
} public void setDelay(long delay) {
this.delay = delay;
} public long getExceptTime() {
return exceptTime;
} public void setExceptTime(long exceptTime) {
this.exceptTime = exceptTime;
} }

然后初始化一个DelayQueue,加入任务。并创建一个线程异步执行。

        DelayQueue<Message> delayqueue = new DelayQueue<>();
Random random = new Random();
for (int i = 0; i < 10; i++) {
Message message = new Message(i, "content" + i, random.nextInt(1000000));
delayqueue.add(message);
} new Thread(new Runnable() { @Override
public void run() {
while (true) {
Message message;
try {
message = delayqueue.take();
System.out.println("message = " + message.getId());
} catch (InterruptedException e) {
e.printStackTrace();
}
} }
}).start();

缺陷

1.毕竟是jdk级别的,不可能做过多的封装。很多API并不是那么好直接使用。比如直接传入一个延迟时间是并不能自动实现的,需要手动封装。

2.DelayQueue并没有长度限制。有内存占用的风险。

3.效率,稳定性方面,在DelayQueue本身肯定是没有问题的,但是在项目中使用,势必需要做一些封装,直接上生产环境心里并没有底。

HashedWheelTimer

netty毕竟是一个大名鼎鼎的框架,广泛使用于业界。它有许多心跳检测等定时任务,使用延时队列来实现。HashedWheelTimer底层数据结构依然是使用DelayedQueue。加上一种叫做时间轮的算法来实现。

关于时间轮算法,有点类似于HashMap。在new 一个HashedWheelTimer实例的时候,可以传入几个参数。

第一,一个时间长度,这个时间长度跟具体任务何时执行没有关系,但是跟执行精度有关。这个时间可以看作手表的指针循环一圈的长度。

然后第二,刻度数。这个可以看作手表的刻度。比如第一个参数为24小时,刻度数为12,那么每一个刻度表示2小时。时间精度只能到两小时。时间长度/刻度数值越大,精度越大。

然后添加一个任务的时候,根据hash算法得到hash值并对刻度数求模得到一个下标,这个下标就是刻度的位置。

然而有一些任务的执行周期超过了第一个参数,比如超过了24小时,就会得到一个圈数round。

简点说,添加一个任务时会根据任务得到一个hash值,并根据时间轮长度和刻度得到一个商值round和模index,比如时间长度24小时,刻度为12,延迟时间为32小时,那么round=1,index=8。时间轮从开启之时起每24/12个时间走一个指针,即index+1,第一圈round=0。当走到第7个指针时,此时index=7,此时刚才的任务并不能执行,因为刚才的任务round=1,必须要等到下一轮index=7的时候才能执行。

如图所示

对于Delayed两个重要实现方法,第一排序,其实是通过hash求商和模决定放入哪个位置。这些位置本身就已经按照时间顺序排序了。第二,延迟时间,已经被封装好了,传入一个延迟的时间就好了。

代码实例:

得到一个延迟队列实例

HashedWheelTimer timer = new HashedWheelTimer(24, //时间轮一圈的长度
TimeUnit.SECONDS,
12);//时间轮的度刻

创建一个任务

TimerTask task = new TimerTask() {

            @Override
public void run(Timeout timeout) throws Exception {
System.out.println("任务执行");
}
};

将任务加入延迟队列

timer.newTimeout(task, 1000, TimeUnit.SECONDS);

总结

以上两种方案都没有实现持久化和分布式。持久化可以借助数据库来达到。分布式的话还是使用消息中间件吧。RabbitMq听说已经可以借助某些参数实现。

使用netty HashedWheelTimer构建简单延迟队列的更多相关文章

  1. redis(六)---- 简单延迟队列

    延迟队列的应用场景也很常见,例如:session的超时过期.自动取消未付款订单等等.redis中有一种数据结构叫做zset,即有序集合.元素类型为String类型,且元素具有唯一性不能重复,每个元素可 ...

  2. Netty构建分布式消息队列实现原理浅析

    在本人的上一篇博客文章:Netty构建分布式消息队列(AvatarMQ)设计指南之架构篇 中,重点向大家介绍了AvatarMQ主要构成模块以及目前存在的优缺点.最后以一个生产者.消费者传递消息的例子, ...

  3. Netty构建分布式消息队列(AvatarMQ)设计指南之架构篇

    目前业界流行的分布式消息队列系统(或者可以叫做消息中间件)种类繁多,比如,基于Erlang的RabbitMQ.基于Java的ActiveMQ/Apache Kafka.基于C/C++的ZeroMQ等等 ...

  4. RabbitMQ 3.6.12延迟队列简单示例

    简介 延迟队列存储的消息是不希望被消费者立刻拿到的,而是等待特定时间后,消费者才能拿到这个消息进行消费.使用场景比较多,例如订单限时30分钟内支付,否则取消,再如分布式环境中每隔一段时间重复执行某操作 ...

  5. 你知道Redis可以实现延迟队列吗?

    最近,又重新学习了下Redis,深深被Redis的魅力所折服,我才知道Redis不仅能快还能慢(我想也这么优秀o(╥﹏╥)o),简直是个利器呀. 咳咳咳,大家不要误会,本文很正经的啦! 好了,接下来回 ...

  6. Rust 实现Netty HashedWheelTimer时间轮

    目录 一.背景 二.延迟队列-时间轮 三.Netty 时间轮源码分析 四.Rust实现HashedWheelTimer 五.总结思考 一.背景 近期在内网上看到一篇文章,文中提到的场景是 系统自动取消 ...

  7. java延迟队列

    大多数用到定时执行的功能都是用任务调度来做的,单身当碰到类似订餐业务/购物等这种业务就不好处理了,比如购物的订单功能,在你的订单管理中有N个订单,当订单超过十分钟未支付的时候自动释放购物车中的商品,订 ...

  8. Dyno-queues 分布式延迟队列 之 基本功能

    Dyno-queues 分布式延迟队列 之 基本功能 目录 Dyno-queues 分布式延迟队列 之 基本功能 0x00 摘要 0x01 Dyno-queues分布式延迟队列 1.1 设计目标 1. ...

  9. Dyno-queues 分布式延迟队列 之 生产消费

    Dyno-queues 分布式延迟队列 之 生产消费 目录 Dyno-queues 分布式延迟队列 之 生产消费 0x00 摘要 0x01 前情回顾 1.1 设计目标 1.2 选型思路 0x02 产生 ...

随机推荐

  1. Java 8-Lambda表达式、方法引用、标准函数接口与流操作、管道操作之间的关系

    1.Lambda表达式与接口之间的关系 只要Lambda表达式的声明形式与接口相一致,在很多情况下都可以替换接口.见如下代码 Thread t1 = new Thread(new Runnable() ...

  2. 工控随笔_18_西门子_WinCC的VBS脚本_07_变量作用域和传值、传址

    在vbs脚本中也存在和其他编程语言一样的概念,那就是变量的作用域,变量的作用域决 定在什么范围内可以访问. 同样的在vbs脚本中对于变量也有一个生命周期, 变量的生命周期决定了变量的存续时间 这个主要 ...

  3. pymsql简单的使用

    不废话直接上代码: import pymysql class MysqlConnection: ''' 单例模式获取数据库链接实例 ''' _instance = None def __new__(c ...

  4. 2.NB-IoT及通信协议

    NB-IoT 1.什么是NB-IoT? NB-IoT全称窄带物联网(Narrow Band IOT),构建于蜂窝网络,只消耗大约180KHz的带宽,可直接部署于GSM网络.UMTS网络或LTE网络,以 ...

  5. Elasticsearch-6.7.0系列(三)5601端口 kibana——ES的UI界面

    https://artifacts.elastic.co/downloads/kibana/kibana-6.7.0-linux-x86_64.tar.gz      Kibana下载 有了elast ...

  6. Mysql 数据库复制

    Mysql 数据库复制 数据库复制是一种同步手段,主库向一台或多台数据库同步数据. 这个功能我自己曾经用uts数据传输工具实现过! 现在学习了一下mysql数据库复制的其他方式 第一个就是最简单的使用 ...

  7. Scrapy CrawlSpider源码分析

    crawl.py中主要包含两个类: 1. CrawlSpider 2. Rule link_extractor:传LinkExtractor实例对象 callback:传”func_name“ cb_ ...

  8. arrayList转换为数据

    ArrayList arrayList = SetTools.loadfile(path); string[] str = (string[])arrayList.ToArray(typeof(str ...

  9. 分析RedisRDB和AOF两种持久化机制的工作原理及优劣势

    一.RDB和AOF两种持久化机制的介绍 RDB持久化机制,对redis中的数据执行周期性的持久化 AOF机制对每条写入命令作为日志,以append-only(追加)的模式写入一个日志文件中,在redi ...

  10. LeetCode 86. Partition List 划分链表 C++

    Given a linked list and a value x, partition it such that all nodes less than x come before nodes gr ...