时间轮(TimingWheel)

news2025/7/20 6:03:21

一、什么是时间轮

时间轮其实就是一种环形的数据结构,可以想象成时钟,分成很多格子,一个格子代表一段时间(这个时间越短,Timer的精度越高)。并用一个双向链表存储放在该格子上的延时任务,同时一个指针随着时间一格一格转动,并执行相应格子中的到期任务。任务通过取模决定放入哪个格子。

二、时间轮结构

tickMs:表示一个槽所代表的时间范围,kafka的默认值为1ms

wheelSize:表示该时间轮有多少个槽,kafka的默认值是20

startMs:表示该时间轮的开始时间

interval:时间轮所能表示的时间跨度,也就是tickMs*wheelSize

currentTime:表示当前时间,也就是时间轮指针指向的时间

buckets:表示TimerTaskList的数组,即各个槽,每个bucket都有一个TimerTaskList。

TimerTaskList:存储TimerTaskEntry的双向链表

TimerTaskEntry:延迟任务,有两个值delayTime和expireTime

delayTime:TimerTask延迟时间

expireTime:TimerTask过期时间

queue:存储TimerTaskList的延迟队列,这些TimerTaskList都被加到这个延迟队列中,expireTime最小的槽会排在队列的最前面。

taskCounter:表示该时间轮的任务总数

三、运行原理

  1. 当定时器新增一个延迟任务时,通过buckets[expiration / tickMs % wheelSize]先计算出它应该属于哪个槽。比如延迟任务的delayMs=2ms,当前时间currentTime是0ms,则expiration=delayMs+startMs=2ms,通过前面的公式算出它应该落于2号槽。并把任务封装成TimerTaskEntry然后加入到TimerTaskList链表中。

  1. kafka会启动一个线程,通过定时器去推动时间轮的指针转动。其实现原理其实就是通过queue.poll()取出放在最前面的槽的TimerTaskList。由于queue是一个延迟队列,如果队列中的expireTime没有到达,该操作会阻塞住,直到expireTime到达。如果通过queue.poll()取到了TimerTaskList,说明该槽里面的任务时间都已经到达。这时候就可以遍历该TimerTaskList中的任务,然后执行对应的操作了。

  1. 定时器中只需持有TimingWheel的第一层时间轮的引用,并不会直接持有其他高层的时间轮,但是每一层时间轮都会有一个引用(overflowWheel)指向更高一层的应用,以此层级调用而可以实现定时器间接持有各个层级时间轮的引用。

四、时间溢出处理

在kafka的默认实现中,tickMs=1Ms,wheelSize=20,这就表示该时间轮所能表示的延迟时间范围是0~20Ms,那如果延迟时间超过20Ms要如何处理呢?Kafka对时间轮做了一层改进,使时间轮变成层级的时间轮。

一开始,第一层的时间轮所能表示时间范围是0~20Ms之间,假设现在出现一个任务的延迟时间是200Ms,那么kafka会再创建一层时间轮,我们称之为第二层时间轮。

也就是第二层时间轮每一个槽所能表示的时间是第一层时间轮所能表示的时间范围,也就是20Ms。槽的数量还是一样,其他的属性也是继承自第一层时间轮。这时第二层时间轮所能表示的时间范围就是0~400Ms了

同理,如果第二层时间轮的时间范围还容纳不了新的延迟任务,就会创建第三层、第四层...

五、时间轮用来解决什么问题

如果一个系统中存在着大量的调度任务,而大量的调度任务如果每一个都使用自己的调度器来管理任务的生命周期的话,浪费cpu的资源并且很低效。

时间轮是一种高效来利用线程资源来进行批量化调度的一种调度模型。把大批量的调度任务全部都绑定到同一个调度器上面,使用这一个调度器来进行所有任务的管理(manager),触发(trigger)以及运行(runnable)。能够高效的管理各种延时任务,周期任务,通知任务等等。

执行过程

1、时间轮

Kafka中一个时间轮TimingWheel是由20个时间格组成,wheelSize = 20;每格的时间跨度是1ms,tickMs = 1ms。参照Kafka,图中也用了20个灰边小圆表示时间格,为了动画演示可以看得清楚,我们这里每个小圆的时间跨度是1s。

整个时间轮的时间跨度就是 tickMs * wheelSize ,也就是 20s。从0s到19s,我们都分别有一个灰边小圆来承载。

Kafka的时间轮还有一个表盘指针 currentTime,表示时间轮当前所处的时间。也就是图中用黑色粗线表示的圆,随着时间推移, 这个指针也会不断前进;

添加定时任务

每一个定时任务都有延时时间delayTime,和过期时间ExpiredTime。比如当前时间是0s,我们添加了个延时时间为2s的任务,那么这个任务的过期时间就是2s,也就是当前时间0s再走两秒,变成了2s的时候,就到了触发这个定时任务的时间。

初始的时候, 时间轮的指针定格在0。此时添加一个超时时间为2s的任务, 那么这个任务将会插入到第二个时间格中。

如果这个时候又插入一个延时时间为8s的任务进来, 这个任务的过期时间就是在当前时间2s的基础上加8s, 也就是10s, 那么这个任务将会插入到过期时间为10s的时间格中。

2、"动态"时间轮

那么如果在当前时间是2s的时候, 插入一个延时时间为19s的任务时,这个任务的过期时间就是在当前时间2s的基础上加19s, 也就是21s。

当前的时间轮是没有过期时间为21s的时间格。这个任务将会插入到过期时间为1s的时间格中

复用时间格

其实呢,当指针定格在2s的位置时, 时间格0s, 1s和2s就已经是过期的时间格。也就是说指针可以用来划分过期的时间格[0,2]和未来的时间格 [3,19]。而过期的时间格可以继续复用。比如过期的时间格0s就变成了20s, 存放过期时间为20s的任务。理解了时间格的复用之后,再看回刚刚的例子,当前时间是2s时,添加延时时间为19s的任务,那么这个任务就会插入到过期时间为21s的时间格中。

3、时间轮升级

如果在当前时间是2s的时候, 插入一个延时时间为22s的任务, 这个任务的过期时间就是在2s的基础上加22s,也就是24s。

显然当前时间轮是无法找到过期时间格为24秒的时间格,因为当前过期时间最大的时间格才到21s。而且我们也没办法像前面那样再复用时间格,因为除了过期时间为2s的时间格,其他的时间格都还没过期呢。当前时间轮无法承载这个定时任务,那么应该怎么办呢?

当然我们可以选择扩展时间轮上的时间格, 但是这样一来,时间轮就失去了意义。

4、层级时间轮

如图是一个两层的时间轮:

第二层时间轮也是由20个时间格组成, 每个时间格的跨度是20s。

图中展示了每个时间格对应的过期时间范围, 我们可以清晰地看到, 第二层时间轮的第0个时间格的过期时间范围是 [0,19]。也就是说, 第二层时间轮的一个时间格就可以表示第一层时间轮的所有(20个)时间格;

添加定时任务

在当前时间是2s的时候, 插入一个延时时间为22s的任务,该任务过期时间为24s。

当第一层时间轮容纳不下时,进入第二层时间轮,并插入到过期时间为[20,39]的时间格中。

如果在当前时间是2s的时候, 插入一个延时时间为350s的任务, 这个任务的过期时间就是在2s的基础上加350s,也就是352s。

从图中可以看到,该任务插入到第二层时间轮过期时间为[340,359]s的时间格中,也就是第17格的位置。

5、"动态"层级时间轮

从图中可以看到,当第一层时间轮的指针定格在1s时,超时时间0s的时间格就过期了。而这个时候,第二层时间轮第0个时间格的时间范围就从[0,19]分为了过期的[0],和未过期的[1,19]。而过期的[0]就会被新的过期时间[400]复用。

第二层时间轮第0个时间格的过期时间范围演变如下:

[0-19]

[400][1,19]

[400,401][2,19]

......

[400,419]

所以,如果在当前时间是2s的时候, 插入一个延时时间为399s的任务, 这个任务的过期时间就是在2s的基础上加399s,也就是401s。如图,这个任务还是会插到第二层时间轮第0个时间格中去。

6、时间轮降级

在当前时间是2s的时候, 插入一个延时时间为22s的任务,该任务过期时间为24s。最后进入第二层时间轮,并插入到过期时间为[20,39]的时间格中。

当指针走到20s的时候,原本超时时间为24s的任务会被取出来,重新加入时间轮。此时该定时任务的延时时间从原本的22s,到现在还剩下4s(22s-18s)。最后停留在第一层时间轮超时时间为24s的时间格,也就是第4个时间格。

从这里可以看出时间轮的巧妙之处,两层时间轮只用了40个数组元素,却可以承载[0-399s]的定时任务。而三层时间轮用60个数组元素,就可以承载[0-7999s]的定时任务!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/347129.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

下一个排列-力扣31-java

一、题目描述整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。例如,arr [1,2,3] ,以下这些都可以视作 arr 的排列:[1,2,3]、[1,3,2]、[3,1,2]、[2,3,1] 。整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地&…

记一次20撸240的沙雕威胁情报提交(2019年老文)

0x01 起因 这是一篇沙雕文章,没什么技术含量,大家娱乐一下就好 前几个月,我的弟弟突然QQ给我发来了一条消息,说要买个QQ飞车的cdk,我作为一个通情达理的好哥哥,自然不好意思回绝,直接叫他发来…

跟着团子学SAP PSS: 如何实现物质在不同法规下的准入管理(配置篇)

全球各地针对化学品的监管出台了很多法规,有相当一部分的法规都是输入准入制:只有在法规中列示的化学物质在当地才具有适销性,未列示的物质只有在一些特殊条件下才能销售。加拿大的DSL(加拿大本国物质清单)&#xff0c…

【Python入门第九天】Python 字符串

字符串字面量 python 中的字符串字面量由单引号或双引号括起。 ‘hello’ 等同于 “hello”。 可以使用 print() 函数显示字符串字面量: 实例 print("Hello") print(Hello)运行实例 用字符串向变量赋值 通过使用变量名称后跟等号和字符串&#xff0…

yolov3-tiny模型转trt的一个实例

感谢作者分享,仓库地址:https://github.com/zombie0117/yolov3-tiny-onnx-TensorRT?tdsourcetags_pcqq_aiomsg该仓库的代码没有使用现有的神经网络框架,如pytorch,tensorflow等,因此环境搭建相对比较容易。仓库作者是…

Docker逃逸

文章目录原理环境搭建Docker 环境判断Docker 容器逃逸特权模式逃逸如何判断是否为特权模式逃逸docker.sock挂载逃逸逃逸Remote API未授权访问未授权访问逃逸容器服务缺陷逃逸影响版本环境搭建逃逸脏牛漏洞逃逸参考原理 docker其实就是一个linux下的进程,它通过Name…

RV1126笔记二十八:RKNN上部署OCR测试

若该文为原创文章,转载请注明原文出处。 本篇文章只是记录下测试OCR的效果,想了解全过程,参考: 【工程部署】手把手教你在RKNN上部署OCR服务(上)_opencv rknn_ocr_三叔家的猫的博客-CSDN博客 【工程部署】手把手教你在RKNN上部署OCR服务(下)_三叔家的猫的博客-CSDN博…

ROS2中使用gazebo仿真时找不到libgazebo_ros_openni_kinect.so

因为ros2的gazebo_ros_pkgs中,已经将该插件移除,或者说将该插件的功能合并到libgazebo_ros_camera.so中, 这里是作者的说明。 合并后,深度相机的用法参考这里。 主要是将type改成depth。还有其他类型的相机,可以参考具…

如何优雅的实现回调函数?

本篇文章又是一期优雅的代码编程介绍———回调函数。 传统的nodejs编程都是这样的 const fs require(fs) fs.readFile(test.txt,utf8, function(err, dataStr){if(err){} }) 嵌套层级如果多了就成回调地狱了。如果我们将这种风格的代码转换成这样呢? const fs …

Word论文排版(1)样式设定

前言:现在很多学校都有 LaTeX 模板了,但是也有很多渣渣学校/学院没提供,而且要求必须提供 Word 版本的大论文!那就只能从 Word 下手了。 Requirements Windows11、Word2016 样式设定 为什么要设置:和 LaTeX 一样&am…

MobaXterm详细使用教程

这一篇MobaXterm详细使用教程,我们来介绍一下如何设置并用MobaXterm来连接Linux服务器。MobaXterm 又名 MobaXVT,是一款增强型终端、X 服务器和 Unix 命令集(GNU/ Cygwin)工具箱。MobaXterm 可以开启多个终端视窗,以最新的 X 服务器为基础的 …

【笔试强训】Day_01

目录 一、选择题 1、 2、 3、 4、 5、 6、 7、 8、 9、 10、 二、编程题 1、组队竞赛 2、删除公共字符 一、选择题 1、 以下for循环的执行次数是&#xff08;&#xff09; for(int x 0, y 0; (y 123) && (x < 4); x); A 、是无限循环 B 、循环次…

从零开始学C

以下是 该如何学习C语言的【思维导图】以及部分重点知识点的【博客链接】。其实C语言并不难&#xff0c;难的是没有人去教&#xff0c;没有耐心去学。不知道从哪下手学习&#xff0c;我将C的知识点做成一个思维导图&#xff0c;以供迷茫的小白参考&#xff0c;哪里不会&#xf…

快速且有效减小代码包的方法

前言当我们在发布一些APP或者小程序等比较小的程序时候&#xff0c;常常会对其主包大小进行一定的规定&#xff0c;若超过推荐的主包大小则性能会被大大影响&#xff0c;或者再严重一点就不给你过审。如微信小程序中也对主包有一定的大小要求。对此一些比较复杂的小程序就需要考…

【机器学习】P1 Cost Function

Cost Functione.g.e.g.e.g. 假设你想要卖一个房子&#xff0c;希望可以用人工智能来算出能卖多少钱。机器学习可以根据提供的数据集合构建出很多模型&#xff0c;但是到底哪种模型最好的拟合你的数据&#xff1f;或者说&#xff0c;哪种模型可以最准确的算出你的卖多少钱&#…

TikTok曝高危漏洞允许一键式帐户劫持,回应称已修复

据The Verge 8月31日消息&#xff0c; TikTok安卓版存在一个高危漏洞&#xff0c;攻击者可能借此实现一键式账户劫持&#xff0c;影响数亿用户 。 微软365防御研究小组在一篇博文中披露了该漏洞的细节&#xff0c; 影响范围为23.7.3之前的安卓版本 。在微软向TikTok报告后&…

如何用 JavaScript 编写你的第一个单元测试

前言 测试代码是使代码安全的第一步。做到这一点的最好方法之一是使用单元测试&#xff0c;确保应用程序中的每个小功能都能发挥其应有的作用–特别是当应用程序处于边缘情况&#xff0c;比如无效的输入&#xff0c;或有潜在危害的输入。 为什么要单元测试 说到单元测试&…

测试开发之Django实战示例 第十章 创建在线教育平台

第十章 创建在线教育平台在上一章&#xff0c;我们为电商网站项目添加了国际化功能&#xff0c;还创建了优惠码和商品推荐系统。在本章&#xff0c;会建立一个新的项目&#xff1a;一个在线教育平台&#xff0c;并创内容管理系统CMS&#xff08;Content Management System&…

OSI模型详解

今天&#xff0c;我们详解OSI&#xff08;Open System Inter-connection Reference Model&#xff09;模型&#xff0c;来看看工业物联网的网络互联和数据互通。 OSI模型 1984年&#xff0c;国际标准化组织&#xff08;International Organization for Standardization&#…

C控制语句(continue,break)

一.continue 3种循环都可以使用continue语句。执行到该语句时&#xff0c;会跳过本次迭代的剩余部分&#xff0c;并开始下一轮迭代 for(count0;count<10;count) { ch getchar(); If(ch \n) continue; putchar(ch&#xff09;&#xff1b; } 这条代码当中当ch等于\…