收藏本站
[公司资质]
[联系我们]
当前位置: 主页 > 597788.net >

定时器有几种实现方式?

时间:2019-10-07 11:55来源:未知 作者:admin 点击:
在开始正题之前,先闲聊几句。有人说,计算机科学这个学科,软件方向研究到头就是数学,硬件方向研究到头就是物理,最轻松的是中间这批使用者,可以不太懂物理,不太懂数学,依旧可以使用计算机作为自己谋生的工具。这个规律具有普适应,再看看定时器这个例

  在开始正题之前,先闲聊几句。有人说,计算机科学这个学科,软件方向研究到头就是数学,硬件方向研究到头就是物理,最轻松的是中间这批使用者,可以不太懂物理,不太懂数学,依旧可以使用计算机作为自己谋生的工具。这个规律具有普适应,再看看“定时器”这个例子,往应用层研究,有 Quartz,Spring Schedule 等框架;往分布式研究,又有 SchedulerX,ElasticJob 等分布式任务调度;往底层实现研究,又有不同的定时器实现原理,工作效率,数据结构…简单上手使用一个框架,并不能体现出个人的水平,如何与他人构成区分度?我觉得至少要在某一个方向有所建树:

  回到这篇文章的主题,我首先会围绕第三个话题讨论:设计实现一个定时器,可以使用什么算法,采用什么数据结构。接着再聊聊第一个话题:探讨一些优秀的定时器实现方案。

  定时器像水和空气一般,普遍存在于各个场景中,一般定时任务的形式表现为:经过固定时间后触发、按照固定频率周期性触发、在某个时刻触发。定时器是什么?可以理解为这样一个数据结构:

  存储一系列的任务集合,并且 Deadline 越接近的任务,拥有越高的执行优先级

  存储一系列的任务集合,并且 Deadline 越接近的任务,拥有越高的执行优先级

  判断一个任务是否到期,基本会采用轮询的方式,每隔一个时间片去检查最近的任务是否到期,并且,在 NewTask 和 Cancel 的行为发生之后,任务调度策略也会出现调整。

  我们主要衡量 NewTask(新增任务),Cancel(取消任务),Run(执行到期的定时任务)这三个指标,分析他们使用不同数据结构的时间/空间复杂度。

  NewTask O(N) 很容易理解,按照 expireTime 查找合适的位置即可;Cancel O(1) ,任务在 Cancel 时,会持有自己节点的引用,所以不需要查找其在链表中所在的位置,即可实现当前节点的删除,这也是为什么我们使用双向链表而不是普通链表的原因是 ;Run O(1),由于整个双向链表是基于 expireTime 有序的,所以调度器只需要轮询第一个任务即可。

  在 Java 中,PriorityQueue是一个天然的堆,可以利用传入的Comparator来决定其中元素的优先级。

  堆与双向有序链表相比,NewTask 和 Cancel 形成了 trade off,但考虑到现实中,定时任务取消的场景并不是很多,所以堆实现的定时器要比双向有序链表优秀。

  HashedWheelTimer 是一个环形结构,可以用时钟来类比,还是原来的味道 北京现代领动插电混动,钟面上有很多 bucket ,每一个 bucket 上可以存放多个任务,使用一个 List 保存该时刻到期的所有任务,同时一个指针随着时间流逝一格一格转动,并执行对应 bucket 上所有到期的任务。任务通过 取模决定应该放入哪个 bucket 。和 HashMap 的原理类似,newTask 对应 put,使用 List 来解决 Hash 冲突。

  以上图为例,假设一个 bucket 是 1 秒,则指针转动一轮表示的时间段为 8s,假设当前指针指向 0,此时需要调度一个 3s 后执行的任务,显然应该加入到 (0+3=3) 的方格中,指针再走 3 次就可以执行了;如果任务要在 10s 后执行,应该等指针走完一轮零 2 格再执行,因此应放入 2,同时将 round(1)保存到任务中。检查到期任务时只执行 round 为 0 的, bucket 上其他任务的 round 减 1。

  再看图中的 bucket5,我们可以知道在 $18+5=13s$ 后,有两个任务需要执行,在 $28+5=21s$ 后有一个任务需要执行。

  时间轮算法的复杂度可能表达有误,我个人觉得比较难算,仅供参考。另外,其复杂度还受到多个任务分配到同一个 bucket 的影响。并且多了一个转动指针的开销。

  Kafka 针对时间轮算法进行了优化,实现了层级时间轮TimingWheel

  如果任务的时间跨度很大,数量也多,传统的HashedWheelTimer会造成任务的round很大,单个 bucket 的任务 List 很长,并会维持很长一段时间。这时可将轮盘按时间粒度分级:

  现在,每个任务除了要维护在当前轮盘的round,还要计算在所有下级轮盘的round。当本层的round为0时,任务按下级round值被下放到下级轮子,最终在最底层的轮盘得到执行。

  JDK 中的Timer是非常早期的实现,在现在看来,它并不是一个好的设计。

  使用Timer实现任务调度的核心是Timer和TimerTask。其中Timer负责设定 TimerTask的起始与间隔执行时间。使用者只需要创建一个TimerTask的继承类,实现自己的run方法,然后将其丢给Timer去执行即可。

  其中 TaskQueue 是使用数组实现的一个简易的堆,前面我们已经介绍过了堆这个数据结构的特点。另外一个值得注意的属性便是TimerThread,一个Timer使用了唯一的线程负责了轮询和任务的执行。Timer的优点在于简单易用,但也因为所有任务都是由同一个线程来调度,因此整个过程是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务。

  由此可见,ScheduleAtFixedRate 是基于固定时间间隔进行任务调度,ScheduleWithFixedDelay 取决于每次任务执行的时间长短,是基于不固定时间间隔的任务调度。

  前面已经介绍过了 Netty 中HashedWheelTimer内部的数据结构,默认构造器会配置轮询周期为 100ms,bucket 数量为 512。其使用方法和 JDK 的使用方式也十分相同。

  由于篇幅限制,我并不打算做详细的源码分析,但上述两行来自 HashedWheelTimer 的代码告诉了我们一个事实:HashedWheelTimer内部也同样是使用了单个线程来进行任务调度。他跟 JDK 的Timer一样,存在”前一个任务执行时间过长,影响后续定时任务执行的问题“。

  毋庸置疑,JDK 的Timer使用的场景是最窄的,完全可以被后两者取代。如何在ScheduledExecutorService和HashedWheelTimer之间如何做选择,还是要区分场景来看待。

  上述的对比,让我们得到了一个最佳实践:在任务量非常大时,使用HashedWheelTimer可以获得性能的提升。例如服务治理框架中的心跳定时任务,当服务实例非常多时,每一个客户端都需要定时发送心跳,每一个服务端都需要定时检测连接状态,这是一个非常适合使用HashedWheelTimer的场景。

  我们需要注意HashedWheelTimer使用的是单线程调度任务,如果任务比较耗时,应当设置一个业务线程池,将HashedWheelTimer当做一个定时触发器,任务的实际执行,交给业务线程池。

  实际使用 HashedWheelTimer 时,应当将其当做一个全局的任务调度器,例如设计成 static 。时刻谨记一点:HashedWheelTimer 对应一个线程,如果每次实例化 HashedWheelTimer,首先是线程会很多,其次是时间轮算法将会完全失去意义。

  ticksPerWheel,tickDuration 这两个参数尤为重要,ticksPerWheel 控制了时间轮中 bucket 的数量,决定了冲突发生的概率,tickDuration 决定了指针拨动的频率,一方面会影响定时的精度,一方面决定 CPU 的消耗量。当任务数量非常大时,考虑增大 ticksPerWheel;当时间精度要求不高时,可以适当加大 tickDuration,不过大多数情况下,不需要 care 这个参数。

  当时间跨度很大时,提升单层时间轮的 tickDuration 可以减少空转次数,但会导致时间精度变低,层级时间轮既可以避免精度降低,又避免了指针空转的次数。如果有长时间跨度的定时任务,则可以交给层级时间轮去调度。此外,也可以按照定时精度实例化多个不同作用的单层时间轮,dayHashedWheelTimer、hourHashedWheelTimer、minHashedWheelTimer,配置不同的 tickDuration,此法虽 low,但不失为一个解决方案。

(责任编辑:admin)
相关内容:
定时器T0的溢出标志为TF0中断 广州stm32单片机培训 如何用C语言实现精确软件定时 单片机定时器的四种工作方式解 平凡生活的突破口全新HUAWEI M
http://www.597788.net4238.com,59499.com,59499.com,www.499500.com,www.37009.com,220338.com香港九龙闪电图库,909188.com,67222.com,www111345com,888212.com
黄大仙六肖王| 摇钱树精英心水论坛| 彩霸王主论坛镇坛之宝| 内部资料 四肖选一肖| 香港六和图库| 香港六和合开奖结果直播| 十二生肖波色对照表| 品特轩高手论坛87654| 小鱼儿六合宝典资料站| 香港马会赛马即时赔率|