使用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 产生 ...
随机推荐
- maven项目pom.xml添加main启动类
pom.xml配置添加main启动类: <build> <finalName>MyApp</finalName> <!-- 最终package打包的jar名称 ...
- [UE4]AttachToComponent的AttachmentRule
官方文档 KeepRelative 将当前相对转换保持为新父级的相对转换 KeepWorld 自动计算相对变换,使附着的组件保持相同的世界变换 SnapToTarget 捕捉转换到附着点
- docker环境安装与开启远程访问
一,安装docker 1,服务器安装 docker yum install docker 直接yum安装版本太低 2,卸载:老版本的Docker在yum中名称为docker或docker-engine ...
- ct
b80e00u9dxwpqw7bt98rm5zmlxt08cxs A3WKXKBHWDUOEOP3EVJA2YRM6JSZPJWGTCQ5BSYAWI4GMSIXOAT2IQ
- Window 无法完成请求的更改,找不到引用的汇编,错误代码 0X80073701
window 10专业版,通过“启用和关闭Window功能”进行安装IIS的部分功能的时候报错: Window 无法完成请求的更改,找不到引用的汇编,错误代码 0X80073701 备注以及尝试: ( ...
- (二)获取Access_token
获取access_token access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token.开发者需要进行妥善保存.access_token的存储至少要保 ...
- python selenium 基本常用操作
最近学习UI自动化,把一些常用的方法总结一下,方便自己以后查阅需要.因本人水平有限,有不对之处多多包涵!欢迎指正! 一.xpath模糊匹配定位元素 武林至尊,宝刀屠龙刀(xpath),倚天不出(css ...
- 初识异步、并发处理纯代码及Demo
多线程Thread 处理 Thread thread = new Thread(()=> { ; i < ; i++) { Console.WriteLine("这是第" ...
- ExcelPackage 读取、导出excel
private static string GetString(object obj) { try { return obj.ToString(); } catch (Exception ex) { ...
- Html.fromHtml采坑篇
在显示复杂的文本样式时,通常采用SpannableString和Html.formHtml来解决需求. 在使用html过程中,通常会出现以下问题: 1.提示Html.formHtml方法过时 解决 使 ...