使用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 产生 ...
随机推荐
- thymeleaf的fragment例子
fragment介绍 fragment类似于JSP的tag,在html中文件中,可以将多个地方出现的元素块用fragment包起来使用. 定义fragment 新建foot.html文件 <!D ...
- react连连看
// Math.floor(t * Math.random()); var isInclude = function (array, element) { let alen = array.len ...
- what is MAC address
MAC Address:media access control address A media access control address (MAC address) is a unique id ...
- Google SketchUp Cookbook: (Chapter 2) Following Paths with Follow Me
软件环境 SketchUp Pro 2018 参考书籍 Google SketchUp Cookbook Follow Me工具 Follow Me工具,将2D图形沿着一条路径挤出生成3D物体. 使用 ...
- Redux 学习总结
1.Redux 设计理念 Web 应用是一个状态机,视图与状态是一一对应的 所有的状态,保存在一个对象里面 2.基本概念和API Redux 的核心就是 store, action, reducer ...
- 转载:MySQL:亲测备份策略实例(线上真实备份案例)
是否为线上库的备份而烦恼过,这里提供一个完整的备份从属数据库的备份方案,亲测可用 说明: 备份从库,按周计,每周进行一次全备 每周一的早上六点进行全备,其他时间备份中继日志 在从库上启用rsync服务 ...
- Python读取文件内容与存储
Python读取与存储文件内容 一..csv文件 读取: import pandas as pd souce_data = pd.read_csv(File_Path) 其中File_path是文件的 ...
- Chapter07 链表(下):如何轻松学出正确的链表代码?
技巧一:理解指针或引用的含义 技巧二:指针丢失和内存泄漏 技巧三:利用哨兵简化实现难度 技巧四:重点留意边界条件处理 如果链表未空时,代码是否能够正常运行 如果链表只包含一个节点时,代码能否正常运行 ...
- AND 初识
框架
- JDK源码中使用的设计模式
结构型模式: 适配器模式: 用来把一个接口转化成另一个接口. java.util.Arrays#asList() javax.swing.JTable(TableModel) java.io.Inpu ...