Netty的Timer管理–开源的魅力

隐身守侯 提交于 2019-11-28 12:23:35

这里的Timer,是指定时器,现代操作系统,定时器无处不在,以至于有些将linux kernel的书,都需要单独列出一章,来将linux是如何管理这些定时器的。管理定时器其实主要的步骤有以下3步:
1.生成定时器(这里面最终要的是给予定时器其到期时间以及到期后要产生的动作)
2.将定时器放入一个数据结构中,以便系统能够定时的扫描检查这些定时器是否到期,是否需要触发
3.定期检查定时器是否到期

一般能够想到的最直观的实现定时器的办法,就是在定时器里插入一个绝对时间,然后将定时器放入一个链表,每次轮询时,将该链表整体轮询一遍,从而检查有哪些定时器到期了。那么这样的时间复杂度为:插入定时器 o(1); 每次轮询o(n).

现在的linux内核中,对timer的管理其实是根据timer离当前时间的远近,将其存放到不同的双向队列数组中,每个队列数组长度一样,比如都是2的8次方;(具体是2的几次方,这个还要依赖其他条件,这里只是举个例子)间隔时间小于2的8次方的,存放在第一个双向队列数组里;间隔时间大于等于2的8次方但小于2的16次方的,则存放在第二个双向队列数组里,以此类推;
然后每个2的8次方个周期,对上面的双向队列数组进行重新安排,比如这个时候,需要将第二个数组里的有些队列移动到地一个数组里。这样在实现时,插入定时器o(1),其只需要计算该timer的时间和当前时间的关系,从而计算出应该将其放置第几个数组的第几个双向队列上(同一个数组的某个双向对列上,存放的都是时间一样的timer)即可(基本上是最简单而直接的hash算法);插入定时器o(1),平时轮询时,只要取最近的那个数组里的对应timer即可,时间复杂度为o(1),每隔指定的周期后(比如2的8次方个周期后)论询一次,这个时候因为要对所有的现存的timer进行调整,最坏时间复杂度是o(n).

而Netty中的Timer管理,则是使用了所谓的Hashed Wheel模式,在数据结构上,其有一个循环数组,这个循环数组有n个bucket(假设为8),可以用来存放Timer链表;Hash Wheel里有2个周期,一个周期是单次轮询的周期,假设为100个Tick;第二个周期是轮询完数组的周期,那么其大小为n*Tick(8*100就为800),假设叫round 周期;那么在每次插入一个timer时,会根据timer的触发时间,计算出其应该存放在哪个bucket的链表中(比如timer距离当前周期为2550tick,那么该Timer就会放在(2550%(n*100Tick))/100Tick=2,这里取celling),在timer插入到bucket中的同时,按照这种算法,相隔round周期整数倍的Timer会存放在相同的bucket的链表中,为了区分round周期,所以每个timer在存放时,还会有一个数值用来村放其距离当前时间还剩多少个round周期(刚才的例子round为3)。当timer插入后,这样轮询的时候就比较方便,轮询时,会有一个cursor,每隔一个周期就加一,论询时该cursor指向数组的哪个bucket,就检查该bucket中的所有timer,主要是检查该timer的round周期是否为0,如果不是0,则将其值减1,然后将其归还(cursor不断增加,当cursor下次指向该bucket时,经过的时间为一个round周期)。而如果round周期已经为0了,则说明该timer需要被触发了,从而触发该timer。这样计算时间复杂度的话,插入的时候,其时间复杂度为o(1),而在轮询的时候,其时间复杂度也可理解为o(1).

Hashed Wheel Timing

Hashed Wheel Timing原理图

比较一下linux内核里的timer管理和netty的timer管理,会发现,如果管理的timer的触发时间都距离当前时间比较近,那么linux和netty的效率应该都差不多(linux这个时候,当间隔的大周期发生的时候,也基本不用调整数组,从而不会发生时间复杂度为o(n)的操作)。而如果管理了较多的距离当前时间很长的timer(此时的linux,会在除了第一个数组外的其他数组里也要存放timer;而netty中,则会在每个bucket的链表中,存放较多的round周期大于0的timer),那么这个时候,linux平时轮询时,;处理的都是确实需要被触发的timer,而netty,很可能会碰到很多不要触发的timer,然后把这些timer的round周期减一,这种情况下,对于平时的轮询,linux是要优于netty;而当碰到大周期时,linux的耗时会超过netty。相当与在这种情形下,netty是把对长时间timer的处理分散在每次轮询中,而linux则是把它集中在一个周期里来做。linux的这种做法,在一般的情景下应该是ok的,但是在realtime os的情况下,应该是有问题的。

相比较与公司自己实现的一个二方库,里面其实也需要用到超时机制,但是实现起来就是很直观,这也没有办法,毕竟大家都时间有限,有的时候还是以快速实现业务为主,没有太多时间把一些技术点考虑的很深远,但是这个时候我们还是可以借鉴一下开源软件中的类似解决方案,拿来为己所用。开源软件能够汇集互联网上很多人的智慧,这可能是很多公司的技术力量都不能够比拟的,这可能也就是开源的魅力所在。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!