本文是【字节可视化系列】Kafka专栏文章。算法
经过本文你将了解到时间轮算法思想,层级时间轮,时间轮的升级和降级。
时间轮,是一种实现延迟功能(定时器)的巧妙算法,在Netty,Zookeeper,Kafka等各类框架中,甚至Linux内核中都有用到。数组
本文将参考Kafka的时间轮做为例子讲解。markdown
开始以前给你们看块宝珀中华年历表。框架
图片来自宝珀官网
oop
至于价格.....这个话题略过。动画
而时间轮,其设计正是来源于生活中的时钟。spa
如图就是一个简单的时间轮:
线程
图中大圆的圆心位置表示的是当前的时间,随着时间推移, 圆心处的时间也会不断跳动。设计
下面咱们对着这个图,来讲说Kafka的时间轮TimingWheel。3d
Kafka时间轮的底层就是一个环形数组,而数组中每一个元素都存放一个双向链表TimerTaskList,链表中封装了不少延时任务。
Kafka中一个时间轮TimingWheel是由20个时间格组成,wheelSize = 20;每格的时间跨度是1ms,tickMs = 1ms。参照Kafka,上图中也用了20个灰边小圆表示时间格,为了动画演示能够看得清楚,咱们这里每一个小圆的时间跨度是1s。
因此如今整个时间轮的时间跨度就是 tickMs * wheelSize ,也就是 20s。从0s到19s,咱们都分别有一个灰边小圆来承载。
Kafka的时间轮还有一个表盘指针 currentTime,表示时间轮当前所处的时间。也就是图中用黑色粗线表示的圆,随着时间推移, 这个指针也会不断前进;
有了时间轮,如今能够往里面添加定时任务了。咱们用一个粉红色的小圆来表示一个定时任务。
讲清楚这些设定后,咱们就开始添加定时任务吧。
初始的时候, 时间轮的指针定格在0。此时添加一个超时时间为2s的任务, 那么这个任务将会插入到第二个时间格中。
当时间轮的指针到达第二个时间格时, 会处理该时间格上对应的任务。在动画上就是让红色的小圆消失!
若是这个时候又插入一个延时时间为8s的任务进来, 这个任务的过时时间就是在当前时间2s的基础上加8s, 也就是10s, 那么这个任务将会插入到过时时间为10s的时间格中。
为了解答上面的问题,咱们先来点魔法, 让时间轮上的时间都动起来!
其实呢,当指针定格在2s的位置时, 时间格0s, 1s和2s就已是过时的时间格。
也就是说指针能够用来划分过时的时间格[0,2]和将来的时间格 [3,19]。而过时的时间格能够继续复用。好比过时的时间格0s就变成了20s, 存放过时时间为20s的任务。
如图是一个两层的时间轮:
回到一开始的问题,在当前时间是2s的时候, 插入一个延时时间为22s的任务,该任务过时时间为24s。
当第一层时间轮容纳不下时,进入第二层时间轮,并插入到过时时间为[20,39]的时间格中。
咱们再来个例子,若是在当前时间是2s的时候, 插入一个延时时间为350s的任务, 这个任务的过时时间就是在2s的基础上加350s,也就是352s。
从图中能够看到,该任务插入到第二层时间轮过时时间为[340,359]s的时间格中,也就是第17格的位置。
一般来讲, 第二层时间轮的第0个时间格是用来表示第一层时间轮的, 这一格是存放不了任务的, 由于超时时间0-20s的任务, 第一层时间轮就能够处理了。
可是! 事情每每没这么简单, 咱们时间轮上的时间格都是能够复用的! 那么这在第二层时间轮上又是怎么体现的呢?
下面是魔法时间, 咱们让时间轮上的过时时间都动起来!
从图中能够看到,当第一层时间轮的指针定格在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个时间格中去。