Netty 时间轮源码解析(时间轮java实现)
ccwgpt 2025-07-21 13:20 2 浏览 0 评论
定时任务在中间件和业务系统中有很多应用,比如:
- 注册中心中定期上报状态的心跳机制。
- RPC 框架中定期扫描请求列表移除超时请求。
- 延迟队列提交未来时间的任务。
- 业务系统每日凌晨跑批处理或报表任务。
Java 原生提供 Timer 和
ScheduledThreadPoolExecutor 类实现定时任务;Netty、akka、Kafka 等框架扩展时间轮算法实现定时任务。
不同的实现有着不同的用途,比如
ScheduledThreadPoolExecutor 适用于精度要求高但请求不大的情形,时间轮适用于精度要求不太高但请求量大的情况。
本文主要讨论时间轮的设计与选型,并结合 Netty 源码详细解析了 Hashed 时间轮的详细实现。
时间轮算法
在介绍算法之前,需要先明确 tick 的概念:tick 是事件触发的最小单位。
时间轮算法通常由四个函数构成,分别是:
- 供 Client 调用:
- 开始任务:startTimer(interval, expiry_func)
- 停止任务:stopTimer(task)
- 当 Timer tick 触发调用:
- 每个 tick 维护任务:perTickBookkeeping
- 任务到期,执行任务:expiryProcessing
我们主要通过 startTimer、stopTimer 和 perTickBookkeeping 这三个函数的时间复杂度来衡量整个时间轮算法的性能,同时内存占用也是要考虑的因素之一。
在《Hashed and Hierarchical Timing Wheels: Efficient Data Structures for Implementing a Timer Facility》 论文中,介绍了几种 Timer 的设计,下表是不同的数据结构下的时间复杂度。
startTimer | stopTimer | perTickBookkeeping | |
有序线性表 | O(n) | O(1) | O(1) |
树 | O(log(n)) | O(1) | O(1) |
简单时间轮 | O(1) | O(1) | O(1) |
Hashed 时间轮(sorted) | 最差 O(n) 平均 O(1) | O(1) | O(1) |
Hashed 时间轮(unsorted) | O(1) | O(1) | 最差 O(n) 平均 O(1) |
带等级时间轮 | O(m) m为时间轮个数 | O(1) | O(1) |
不同的数据结构适用于不同的数据量。Netty 采用的是 Hashed 时间轮(unsorted),Kafka 采用的是带等级的时间轮。
下面我们基于 Netty 来探究它是如何高效实现时间轮算法的。
Netty 时间轮
代码示例
public static void main(String[] args) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
HashedWheelTimer timer = new HashedWheelTimer(10, TimeUnit.MILLISECONDS);
System.out.println(LocalDateTime.now().format(formatter));
Timeout task = timer.newTimeout(timeout -> {
System.out.println(LocalDateTime.now().format(formatter));
}, 3, TimeUnit.SECONDS);
task.cancel();
}
通过 HashedWheelTimer 构造函数创建时间轮,然后通过 newTimeOut 方法提交任务,指定任务回调函数以及触发时间点,时间轮会在指定时间触发回调函数,完成定时任务逻辑。
对照前面的函数,timer.newTimeout 等同于 startTimer 操作。task.cancel 等同于 stopTimer。在 HashedWheelTimer 内部,每个 tick 触发的操作为 perTickBookkeeping,当任务时间到了时触发 expiryProcessing 操作。
数据结构
上图是 HashedWheelTimer 的结构示意图,由数组和双向链表组成,和 HashMap 的结构类似,这也是被称为 HashedWheelTimer 的原因。
构造函数
HashedWheelTimer 提供了多个构造函数,这里我们主要关注 tickDuration、ticksPerWheel 这两个属性。
tickDuration 用来配置每个 tick 的间隔。值越小,时间轮的精度越高,单位时间内触发次数越多,因此性能会有一定的缩减。最小可以设置为 1ms,默认是 100ms,如无特殊情况,走默认即可。
ticksPerWheel 代表数组的长度。值越大,数据的散列程度就越好,查找效率就越高,但内存开销就会大些。值越小,内存开销小,但冲突会变多,查找效率低。当我们指定该值时,和 HashMap 一样,系统会找大于等于该值的最小的二的倍数作为数组长度,如无特殊情况,默认的 512 就可以了。
提交&取消任务
当我们调用 newTimeOut 提交任务时,系统不会直接将任务写到时间轮中,而是写入暂存队列,等待后续线程的触发。
当对任务调用 cancel 方法时,系统也不会去时间轮中删除任务,同样地写入取消任务队列,等待后续的触发。
tick 操作
当构造完毕后,会启动 worker 线程来触发 tick 操作。
如何保证 tick 按时触发
当 worker 线程启动后,会初始化当前时间为 startTime。任务的到期时间都是基于 startTime 的相对时间。
这里假设每隔 tick 间隔 1000ms,此时 currentTime - startTime = 2500ms,距离下一个 tick 还有 500ms,此时通过 Thread.sleep(500) 休眠,等到唤醒时继续判断,直到相对时间大于 3000,此时触发 tick 操作。
tick 触发
tick 触发主要做三件事情:处理待取消任务、处理待提交任务和处理超时逻辑。
- 取消任务 通过 queue 的 poll 操作拉取待取消任务,然后将任务在双向链表中移除即可。
- 提交任务 从提交任务的 queue 中 poll 最多 10w 条数据(避免生产者持续添加任务,造成 tick 线程繁忙)然后根据任务触发时间 / tickDuration 确认任务应当在第几个 tick 被触发,将其添加到对应的数组下标的双向链表尾部。Math.max(calculated, tick) 是为了避免任务在提交时已经过期。如果按照 calculated 结果,很可能将任务放到 tick - 1 的位置上,这个任务只能等到时间轮再转一圈之后才能被调用到。long calculated = timeout.deadline / tickDuration;
timeout.remainingRounds = (calculated - tick) / wheel.length;
final long ticks = Math.max(calculated, tick); // Ensure we don't schedule for past.
int stopIndex = (int) (ticks & mask);
HashedWheelBucket bucket = wheel[stopIndex];
bucket.addTimeout(timeout); - 执行任务 遍历双向链表中的任务,如果剩余圈数小于等于 0,则执行过期逻辑,然后执行移除节点逻辑。
总结
- Timer 算法有很多种,Netty 使用的是 Hashed Wheel,Kafka 使用的是 Hierarchical Wheel。
- 时间轮适用于时间精度要求不是特别高的场景,这能够满足常见的业务场景。
- Netty 时间轮 startTimer 是 O(1),stopTimer 是 O(1),perTickBookkeeping 是 O(n/m),其中 n 是所有元素的数量,m 是时间轮中 tick 的个数。
- Netty 时间轮中任务调度和执行都是单线程的,提交的任务尽量不要包含复杂逻辑,减少时间占用。
- 上一篇:Autodesk基于Mesos的通用事件系统架构
- 已经是最后一篇了
相关推荐
- ForkJoinPool的了解与使用(fork-join)
-
ForkJoinPool是一个强大的Java类,用于处理计算密集型任务。使用ForkJoinPool分解计算密集型任务并并行执行它们以获得更好的Java应用程序性能。它的工作原理是将任务分解为更小的子...
- Netty 时间轮源码解析(时间轮java实现)
-
定时任务在中间件和业务系统中有很多应用,比如:注册中心中定期上报状态的心跳机制。RPC框架中定期扫描请求列表移除超时请求。延迟队列提交未来时间的任务。业务系统每日凌晨跑批处理或报表任务。Java原...
- Autodesk基于Mesos的通用事件系统架构
-
【编者按】本文由AutodeskCloud软件架构师OlivierPaugam撰写,解释了如何集合Mesos、Kafka、RabbitMQ、Akka、Splunk、Librato、EC2等基础设施...
- 全局视角看技术-Java多线程演进史
-
作者:京东科技文涛全文较长共6468字,语言通俗易懂,是一篇具有大纲性质的关于多线程的梳理,作者从历史演进的角度讲了多线程相关知识体系,让你知其然知其所以然。前言2022年09月22日,JDK19发...
- 为什么应该使用Dapr来构建事件驱动的微服务?
-
微服务架构从本质上来说是分布式的。构建微服务总是会遇到极具挑战性的问题,比如说弹性服务调用、分布式事务处理、按需扩容以及严格一次(exactly-once)的消息处理。将微服务放在Kubernet...
- WEB前端开发学习流程(web前端开发简明教程)
-
相对web后端开发来说,web前端开发对大部分初学编程者比较友好,而且入门门槛低,就业范围广。是大部分转行学IT的一个首选方向。web前端开发工程师,主要进行网站浏览器的开发、优化、布局的工作。在了解...
- 《s24z 编程指南》大纲(AI 提示词)
-
由于AIGC的迅速发展,本教程《s24z编程指南》,尝试用如下方法:准备《编程指南》的大纲,按章节划分,每小节由相关知识点和文字组成。每次将一小部分文本,以提示词的形式,送入Kimi或Ch...
- 有哪些常用的Python后端开发框架?
-
以下为你介绍一些常用的Python后端开发框架,包含各自的特点、适用场景与示例代码:Flask特点:轻量级、灵活,核心代码简洁,几乎不强制开发者使用特定的工具和库,开发者可按需添加扩展。适用场景...
- 数学分析的结构(数学分析的结构方法)
-
一、基础结构层实数系统与集合论数学分析的根基建立在实数连续之上,通过集合论(如公理化集合论)定义数学对象的抽象结构。例如,实数集的完备性公理是数学分析区别于其他数学分支的关键特征。此外,点集拓扑学(如...
- 新手在学习Web前端时需要学习的内容汇总
-
Web前端开发因为入行门槛低,是很多人转行IT开发行业的首选,但想要成为一名合格的Web前端开发工程师同样要具备过硬的专业技能,而且想要学成后高薪快速的就业,过硬的技术是基本条件。那么,新手小白学习W...
- 基于 Kotlin KMP 实现 HarmonyOS 与 Android 双平台 SDK 开发实践
-
背景随着鸿蒙平台的进一步发展,大家的态度也逐渐从观望转向实际投入,越来越多的公司开始考虑将自家应用迁移到鸿蒙平台。但是这一过程并非想象中的那么简单,尤其对于已经存在很多年的大型项目来说,直接投入大量人...
- 爱奇艺 App 中台技术实践(爱奇艺 app 中台技术实践在哪)
-
本文来自爱奇艺研究员在ArchSummit全球架构师峰会上的演讲整理,将为大家分享爱奇艺打造移动中台的过程。爱奇艺移动中台的建设过程可分为组件解耦、组件定制化和平台化,未来会利用平台发现、沉淀和复...
- 软件开发|同样的功能需求,为什么有的软件公司报价高?有的低?
-
最近有个朋友问我:同样的功能需求,为什么有的公司报价高?有的公司报价低?其实,有很多创业的朋友,在寻找技术开发公司的时候,经常会遇到这个困惑,一样的功能需求,不同的公司有不同的报价,有的差别还很大,那...
- 零基础要怎么学习Web前端?Web前端学习路径分享
-
Web前端因为薪资高、入行门槛低,成为很多人转行进入IT行业的首选。对于零基础的人来说,学习之前一定要想清楚为什么而学习Web前端,给自己一个清晰的定位,摆正心态。如果还不清楚学习路线,可以参考千锋武...
- MICROCHIP/微芯 KSZ9031RNXIA 以太网芯片
-
特征o适用于IEEE802.3应用的单片10/100/1000Mbps以太网收发器oGMII/MII标准接口,3.3V/2.5V/1.8V容错I/Oo自动协商以自动选择最高链路连接速度(10/10...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- 框架图 (58)
- flask框架 (53)
- quartz框架 (51)
- abp框架 (47)
- jpa框架 (47)
- springmvc框架 (49)
- 分布式事务框架 (65)
- scrapy框架 (56)
- shiro框架 (61)
- 定时任务框架 (56)
- java日志框架 (61)
- JAVA集合框架 (47)
- mfc框架 (52)
- abb框架断路器 (48)
- ui自动化框架 (47)
- beego框架 (52)
- java框架spring (58)
- grpc框架 (65)
- tornado框架 (48)
- 前端框架bootstrap (54)
- ppt框架 (48)
- 内联框架 (52)
- cad怎么画框架 (58)
- ssm框架实现登录注册 (49)
- oracle字符串长度 (48)