使用netty HashedWheelTimer构建简单延迟队列
背景
最近项目中有个业务,需要对用户新增任务到期后进行业务处理。使用定时任务定时扫描过期时间,浪费资源,且不实时。只能使用延时队列处理。
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构建简单延迟队列的更多相关文章
- redis(六)---- 简单延迟队列
延迟队列的应用场景也很常见,例如:session的超时过期.自动取消未付款订单等等.redis中有一种数据结构叫做zset,即有序集合.元素类型为String类型,且元素具有唯一性不能重复,每个元素可 ...
- Netty构建分布式消息队列实现原理浅析
在本人的上一篇博客文章:Netty构建分布式消息队列(AvatarMQ)设计指南之架构篇 中,重点向大家介绍了AvatarMQ主要构成模块以及目前存在的优缺点.最后以一个生产者.消费者传递消息的例子, ...
- Netty构建分布式消息队列(AvatarMQ)设计指南之架构篇
目前业界流行的分布式消息队列系统(或者可以叫做消息中间件)种类繁多,比如,基于Erlang的RabbitMQ.基于Java的ActiveMQ/Apache Kafka.基于C/C++的ZeroMQ等等 ...
- RabbitMQ 3.6.12延迟队列简单示例
简介 延迟队列存储的消息是不希望被消费者立刻拿到的,而是等待特定时间后,消费者才能拿到这个消息进行消费.使用场景比较多,例如订单限时30分钟内支付,否则取消,再如分布式环境中每隔一段时间重复执行某操作 ...
- 你知道Redis可以实现延迟队列吗?
最近,又重新学习了下Redis,深深被Redis的魅力所折服,我才知道Redis不仅能快还能慢(我想也这么优秀o(╥﹏╥)o),简直是个利器呀. 咳咳咳,大家不要误会,本文很正经的啦! 好了,接下来回 ...
- Rust 实现Netty HashedWheelTimer时间轮
目录 一.背景 二.延迟队列-时间轮 三.Netty 时间轮源码分析 四.Rust实现HashedWheelTimer 五.总结思考 一.背景 近期在内网上看到一篇文章,文中提到的场景是 系统自动取消 ...
- java延迟队列
大多数用到定时执行的功能都是用任务调度来做的,单身当碰到类似订餐业务/购物等这种业务就不好处理了,比如购物的订单功能,在你的订单管理中有N个订单,当订单超过十分钟未支付的时候自动释放购物车中的商品,订 ...
- Dyno-queues 分布式延迟队列 之 基本功能
Dyno-queues 分布式延迟队列 之 基本功能 目录 Dyno-queues 分布式延迟队列 之 基本功能 0x00 摘要 0x01 Dyno-queues分布式延迟队列 1.1 设计目标 1. ...
- Dyno-queues 分布式延迟队列 之 生产消费
Dyno-queues 分布式延迟队列 之 生产消费 目录 Dyno-queues 分布式延迟队列 之 生产消费 0x00 摘要 0x01 前情回顾 1.1 设计目标 1.2 选型思路 0x02 产生 ...
随机推荐
- 工控随笔_02_西门子_WinCC的IO域利用C脚本返回值
WinCC的输入输出域用来显示信息或者接受操作人员的输入.当作为显示功能时,只有直接的变量连接 才能正常的显示,如果使用动态对话框进行设置且用了表达式则不能正确显示. 但是有时候我们在WinCC变量管 ...
- C# 6.0:新的Dictionary Initializer
初始化Dictionary不是什么新东西,你可以简单的通过Collection Initializer来初始化一个Dictionary,这是从C#3.0就有的特性.Collection Initial ...
- javase高级
静态代理:需要代理对象和目标对象实现一样的接口.同一个接口,一个目标类实现,一个代理类实现,代理类除了目标类的方法还有别的增强方法优点:可以在不修改目标对象的前提下扩展目标对象的功能.缺点:1冗余.由 ...
- BeanUtils使用
1.BeanUtils.populate 可以把一个map中的属性拷贝到实体javaBean,例子: Student: package com.cy.model; import org.apache. ...
- 必做课下作业MyCP
20175227张雪莹 2018-2019-2 <Java程序设计> 必做课下作业MyCP 要求 编写MyCP.java 实现类似Linux下cp XXX1 XXX2的功能,要求MyCP支 ...
- 文件下载及header方法介绍
文件下载: 文件下载是浏览器一个功能,我们用php,把一个文件转化成浏览器无法解析的文件,浏览器就会认为,他是下载文件或无效文件. 主要依靠:header() 函数: header() 方法用于客户端 ...
- 初学者常用的LINUX命令
测试初学者常用的LINUX命令 一.常用管理命令:1. shutdown -h now 关机命令2. shutdown -r now (reboot) 立即重启命令 3. clear 清屏命令 4. ...
- 刘志梅 2017710101152《面向对象程序设计(java)》 第十周学习总结
实验十 泛型程序设计技术 实验时间 2018-11-1 1.实验目的与要求 (1)泛型程序设计:意味着编写的代码可以被很多不同类型的对象所重用.(ArrayList类可以聚集任何类型的对象) 如果在 ...
- 神经网络常用的Numpy功能笔记
数组初始化 x=np.array([[1,2]]) x=np.zeros((2,3)) 生成随机数 w=np.random.randn(2,3) PIL image转换成array img = np. ...
- 文件数据缓存(key-Value)
为了解决大量数据缓存,消耗内存过多的问题,特别实现了文件缓存:该缓存主要是应用于多存少读的情况,一般我们做缓存是实现将数据放在内存中或者数据库中:放在内存中就会消耗很大内存,尤其在高并发大数据缓存时, ...