从定时任务说起

自然界中定时任务无处不在,太阳每天东升西落,候鸟的迁徙,树木的年轮,人们每天按时上班,每个月按时发工资、交房租,四季轮换,潮涨潮落,等等,从某种意义上说,都可以认为是定时任务。

大概很少有人想过,这些“定时”是怎样做到的。当然,计算机领域的同学们可能对此比较熟悉,毕竟工作中的定时任务也是无处不在的:每天凌晨更新一波数据库,每天9点发一波邮件,每隔10秒钟抢一次火车票。。。

至于怎么实现的?很简单啊,操作系统的crontab,spring框架的quartz,实在不行Java自带的ScheduledThreadPool都可以很方便的做到定时任务的管理调度。

当你熟练的敲下

* * 9 * * ?

等着神奇的事情发生时,你是否想过背后的“玄机”?

初识时间轮

大概去年的时候,业务需要实现一个时间调度的工具,定时生成报表,同组的哥们儿想了一个取巧的办法:

  1. 启动时从DB读取cron表达式解析,算出该任务下次执行的时间。
  2. 下次执行时间-当前时间=时间差。
  3. 向ScheduleThreadPool线程池中提交一个延迟上面算出来的时间差的执行的任务。
  4. 任务执行时,算一下这个任务下次执行的时间,算时间差,提交到线程池。
  5. 当任务需要取消时,直接调用线程池返回的Future对象的cancel()方法就行了。

当时稍微想了⼀ScheduleThreadPool是怎么做到定时执⾏提交过来的任务的,⼤概有个模糊的概念,后来就把这事忘了。再后来,⼀次在地铁上看到⼀篇⽂章,讲了⼀种叫做时间轮的定时任务调度思想,感觉想法很不错,当年那个模糊的概念似乎清晰了很多,再后来,⼀个偶然的机会,⽹上搜了⼀下,竟然有⼀篇专门讲解时间轮算法的论⽂,顿时兴奋⽆⽐,赶紧打印下来,在上班的地铁上读了半个⽉,总算读完了。

戳这⾥下载:《Hashed and Hierarchical Timing Wheels》

论⽂中的思路很简单但也⼗分巧妙,对算法不断的改进对⽐,各种操作系统,框架中的基于时间的调度算法都是基于时间轮的思想实现的。下⾯我们来看看,这个神奇的时间轮到底是怎样实现定时任务的调度的。

绝对时间和相对时间

定时任务⼀般有两种:

  1. 约定⼀段时间后执⾏。
  2. 约定某个时间点执⾏。

聪明的你会很快发现,这两者之间可以相互转换,⽐如给你个任务,要求12点执⾏,你看了⼀眼时间,发现现在是9点钟,那么你可以认为这个任务三个⼩时候执⾏。

同样的,给你个任务让你3个⼩时后执⾏,你看了⼀眼现在是9点钟,那么你当然可以认为这个任务12点钟执⾏。

我们先来考虑⼀个简单的情况,你接到三个任务A、B、C(都转换成绝对时间),分别需要再3点钟,4点钟和9点钟执⾏,正当百思不得其解时,不经意间你瞅了⼀眼墙上的钟表,瞬间来了灵感,如醍醐灌顶,茅塞顿开:

如上图中所⽰,我只需要把任务放到它需要被执⾏的时刻,然后等着时针转到这个时刻时,取出该时刻放置的任务,执⾏就可以了。 这就是时间轮算法最核⼼的思想了。 什么?时针怎么转? while-true-sleep 下⾯让我们⼀点⼀点增加复杂度。

需要重复执⾏多次的任务

多数定时任务是需要重复执⾏,⽐如每天上午9点执⾏⽣成报表的任务。对于重复执⾏的任务,其实我们需要关⼼的只是下次执⾏时间,并不关⼼这个任务需要循环多少次,还是那每天上午9点的这个任务来说。

  1. ⽐如现在是下午4点钟,我把这个任务加⼊到时间轮,并设定当时针转到明天上午九点(该任务下次执⾏的时间)时执⾏。
  2. 时间来到了第⼆天上午九点,时间轮也转到了9点钟的位置,发现该位置有⼀个⽣成报表的任务,拿出来执⾏。
  3. 同时时间轮发现这是⼀个循环执⾏的任务,于是把该任务重新放回到9点钟的位置。
  4. 重复步骤2和步骤3。 如果哪⼀天这个任务不需要再执⾏了,那么直接通知时间轮,找到这个任务的位置删除掉就可以了。

由上⾯的过程我们可以看到,时间轮⾄少需要提供4个功能:

  1. 加⼊任务
  2. 执⾏任务 (继续放回)
  3. 删除任务
  4. 沿着时间刻度前进

同⼀时刻存在多个任务

上⾯说的是同⼀个时刻只有⼀个任务需要执⾏的情况,更通⽤的情况显然是同⼀时刻可能需要执⾏多个任务,⽐如每天上午九点除了⽣成报表之外,还需要执⾏发送邮件的任务,需要执⾏创建⽂件的任务,还需执⾏数据分析的任务等等,于是你刚才可能就⽐较好奇的时间轮的数据结构到现在可能更加好奇了,那我们先来说说时间轮的数据结构吧。

时间轮的数据结构

⾸先,时钟可以⽤数组或者循环链表表⽰,这个每个时钟的刻度就是⼀个槽,槽⽤来存放该刻度需要执⾏的任务,如果有多个任务需要执⾏呢?每个槽⾥⾯放⼀个链表就可以了,就像下⾯图中这样:

同⼀时刻存在多个任务时,只要把该刻度对应的链表全部遍历⼀遍,执⾏(扔到线程池中异步执⾏)其中的任务即可。

时间刻度不够⽤怎么办?

如果任务不只限定在⼀天之内呢?⽐如我有个任务,需要每周⼀上午九点执⾏,我还有另⼀个任务,需要每周三的上午九点执⾏。⼀种很容易想到的解决办法是:

增⼤时间轮的刻度

⼀天24个⼩时,⼀周168个⼩时,为了解决上⾯的问题,我可以把时间轮的刻度(槽)从12个增加到168个,⽐如现在是星期⼆上午10点钟,那么下周⼀上午九点就是时间轮的第9个刻度,这周三上午九点就是时间轮的第57个刻度,⽰意图如下:

仔细思考⼀下,会发现这中⽅式存在⼏个缺陷:

  1. 时间刻度太多会导致时间轮⾛到的多数刻度没有任务执⾏,⽐如⼀个⽉就2个任务,我得移动720次,其中718次是⽆⽤功。

  2. 时间刻度太多会导致存储空间变⼤,利⽤率变低,⽐如⼀个⽉就2个任务,我得需要⼤⼩是720的数组,如果我的执⾏时间的粒度精确到秒,那就更恐怖了。

于是乎,聪明的你脑袋⼀转,想到另⼀个办法:

列表中的任务中添加round属性

这次我不增加时间轮的刻度了,刻度还是24个,现在有三个任务需要执⾏,

  1. 任务一每周二上午九点。
  2. 任务⼆每周四上午九点。
  3. 任务三每个⽉12号上午九点。

⽐如现在是9⽉11号星期⼆上午10点,时间轮转⼀圈是24⼩时,到任务⼀下次执⾏(下周⼆上午九点),需要时间轮转过6圈后,到第7圈的第9个刻度开始执⾏。

任务⼆下次执⾏第3圈的第9个刻度,任务三是第2圈的第9个刻度。

⽰意图如下:

时间轮每移动到⼀个刻度时,遍历任务列表,把round值-1,然后取出所有round=0的任务执⾏,若是重复任务,执行完后将round恢复到最初值,再将任务重新放入时间轮中。

这样做能解决时间轮刻度范围过⼤造成的空间浪费,但是却带来了另⼀个问题:时间轮每次都需要遍历任务列表,耗时增加,当时间轮刻度粒度很⼩(秒级甚⾄毫秒级),任务列表⼜特别长时,这种遍历的办法是不可接受的。

当然,对于⼤多数场景,这种⽅法还是适⽤的。有没有既节省空间,⼜节省时间的办法呢? 答案是有的,正如《Hashed and Hierarchical Timing Wheels》标题中提到的,有⼀种分层时间轮,可以解决做到既节省空间,⼜节省时间:

分层时间轮

分层时间轮是这样⼀种思想:

  1. 针对时间复杂度的问题:不做遍历计算round,凡是任务列表中的都应该是应该被执⾏的,直接全部取出来执⾏。

  2. 针对空间复杂度的问题:分层,每个时间粒度对应⼀个时间轮,多个时间轮之间进⾏级联协作。

第一点很好理解,第二点有必要举个例子来说明:
比如我有三个任务:

  1. 任务⼀每周⼆上午九点。
  2. 任务⼆每周四上午九点。
  3. 任务三每个⽉12号上午九点。

三个任务涉及到四个时间单位:⼩时、天、星期、⽉份。

拿任务三来说,任务三得到执⾏的前提是,时间刻度先得来到12号这⼀天,然后才需要关注其更细⼀级的时间单位:上午9点。

基于这个思想,我们可以设置三个时间轮:⽉轮、周轮、天轮。

  • ⽉轮的时间刻度是天。
  • 周轮的时间刻度是天。
  • 天轮的时间刻度是⼩时。

初始添加任务时,任务⼀添加到天轮上,任务⼆添加到周轮上,任务三添加到⽉轮上。

三个时间轮以各⾃的时间刻度不停流转。

当周轮移动到刻度2(星期⼆)时,取出这个刻度下的任务,丢到天轮上,天轮接管该任务,到9点执⾏。

同理,当⽉轮移动到刻度12(12号)时,取出这个刻度下的任务,丢到天轮上,天轮接管该任务,到9点执⾏。

这样就可以做到既不浪费空间,又不浪费时间。

整体的⽰意图如下所⽰:

round时间轮和分层时间轮的一点比较

相比于round时间轮思想,采用分层时间轮算法的优点在于:只需要多耗费极少的空间(从1个时间轮到3个时间轮),就能实现多线程在效率上的提高(一个时间轮是一个线程去行走,3个时间轮可以3个线程行走)。当然这是相对的,若提交的任务都是每隔几个小时重复执行,那显然小时时间轮比月、周、小时时间轮组合的耗费空间少,且执行时间还相同。

时间轮的应⽤

时间轮的思想应⽤范围⾮常⼴泛,各种操作系统的定时任务调度,Crontab,还有基于java的通信框架Netty中也有时间轮的实现,⼏乎所有的时间任务调度系统采⽤的都是时间轮的思想。

⾄于采⽤round型的时间轮还是采⽤分层时间轮,看实际需要吧,时间复杂度和实现复杂度的取舍。


参考链接:
https://its301.com/article/qq_34039868/105384808
https://cloud.tencent.com/developer/article/1815722

那些惊艳的算法—时间轮任务调度(sunwind整理)相关推荐

  1. 那些惊艳的算法们(三)—— 时间轮

    从定时任务说起 自然界中定时任务无处不在,太阳每天东升西落,候鸟的迁徙,树木的年轮,人们每天按时上班,每个月按时发工资.交房租,四季轮换,潮涨潮落,等等,从某种意义上说,都可以认为是定时任务. 大概很 ...

  2. 卧槽,这才是最惊艳的算法大赛!

    "人工智能"正在深入生活的每个角落,也已经成为一个时代的代名词.半个世纪以来,AI 发展的方向不断变化,从作为计算工具提供便利到"赋予智慧",科学家.从业人员与 ...

  3. 从 Kafka 看时间轮算法设计

    欢迎关注方志朋的博客,回复"666"获面试宝典 来源:https://juejin.cn/post/7047405443961847816 前言 Kafka 中有很多延时操作,比如 ...

  4. 时间轮 (史上最全)

    缓存之王 Caffeine 中,涉及到100w级.1000W级.甚至亿级元素的过期问题,如何进行高性能的定时调度,是一个难题. 注: 本文从 对 海量调度任务场景中, 高性能的时间轮算法, 做了一个 ...

  5. 玛塔机器人函数_玛塔学员世界机器人大赛首秀:能编写超50道指令,创意海洋保护方案超惊艳!...

    原标题:玛塔学员世界机器人大赛首秀:能编写超50道指令,创意海洋保护方案超惊艳! 前段时间,一则关于AI技术的"乌龙"让人忍俊不禁. 在一场足球赛中,虽然具有AI追踪技术的摄像头被 ...

  6. java 时间轮算法_时间轮算法(TimingWheel)是如何实现的?

    前言 我在2. SOFAJRaft源码分析-JRaft的定时任务调度器是怎么做的?这篇文章里已经讲解过时间轮算法在JRaft中是怎么应用的,但是我感觉我并没有讲解清楚这个东西,导致看了这篇文章依然和没 ...

  7. 用c语言实现对n个进程采用“短进程优先”算法的进程调度_为什么Linux CFS调度器没有带来惊艳的碾压效果?...

    文章转自公众号"人人都是极客" 但凡懂Linux内核的,都知道Linux内核的CFS进程调度算法,无论是从2.6.23将其初引入时的论文,还是各类源码分析,文章,以及Linux内核 ...

  8. 时间轮算法HashedWheelTimer

    文章目录 一.HashedWheelTimer是什么? 二.能干什么?为什么需要这个东西? 优点 适用场景 三.怎么用?使用步骤 1.引入pom 2.使用举例 四.时间轮原理 五.使用注意点 1.一个 ...

  9. .Net之时间轮算法(终极版)定时任务

    TimeWheelDemo 一个基于时间轮原理的定时任务 对时间轮的理解 其实我是有一篇文章(.Net 之时间轮算法(终极版)[1])针对时间轮的理论理解的,但是,我想,为啥我看完时间轮原理后,会采用 ...

最新文章

  1. 判断设备网络状态_生成树RSTP,快速生成树协议,交换网络必用的破环协议,面试必备...
  2. Eclipse的ExtJs智能提示
  3. 黄金价格预测:如何将时序数据处理成监督学习数据
  4. 《剑指offer》翻转单词顺序列
  5. git上传代码,合并代码,分支相关
  6. 为什么一点onclick按钮就提交表单?
  7. 快慢指针:141. 环形链表(判断是否存在环路)
  8. MySQL数据库datetime类型不能为空值的问题
  9. php curl如何解决分页,一段PHP的分页程序,报错,该如何解决
  10. AssertJ断言系列一
  11. php include不可用,无法设置PHP include_path
  12. 多播泡妞宝典---IGMP
  13. c++绝对值函数_Python自带自定义高阶函数实战
  14. hspice linux 软件,Hspice 200803 linux安装(亲测可行)
  15. python中减号怎么打_python减号
  16. HDFS中NameNode和DataNode的作用
  17. 在windows生成SSH秘钥连接linux远程主机
  18. HPET(High Precision Event Timer)简要说明
  19. php 支付宝账单监控,支付宝账单监测 收款监测 实时监控
  20. PySpark之DataFrame的常用函数(创建、查询、修改、转换)

热门文章

  1. 870-Linux下解决高并发socket最大连接数限制
  2. Linux修改配置文件(自动)
  3. 单因素模糊评价matlab,模糊综合评判matlab源程序
  4. iOS-关于微信支付
  5. Python批量合并处理B站视频
  6. arm指令bne.w改成b,即无条件跳转
  7. 自己忙碌十年,发现空空的,该填满了
  8. 乐高凯德机器人_乐高机器人体验课
  9. ORA-28547 连接服务器失败
  10. 求 一个 quality center explorer 9.0的 百度网盘 或是别的下载地址的链接