kafka时间轮解析

概述

    这篇博文的起源在于阿里的公众号里面有一篇文章讲菜鸟的同学在造一个关于时间轮定时器的文章,然后在网上搜索资料发现其实在好多开源的软件里面已经有了,最后选择了kafka里面的定时器实现来加深自己的理解。这个概念有点绕,我也尽量把核心的点讲解清楚,博文末尾的两旁参考文献其实是讲解的比较清楚的,我应该会盗用里面的图来实现来帮助自己把核心点讲清楚。

    在理解kafka的时间轮定时器的概念的时候,我们需要提前了解下java里面的DelayQueue的概念,因为在kafka的时间轮定时器其实是基于DelayQueue来实现的。

    最后我希望一定要好好看博文末尾的参考文献,将这篇博文+参考文献就可以把时间轮理解的很清楚。


定时器选型

    传统方案

        对于实现一个定时任务,一般的做法是将定时任务写入数据库,通过一个线程定时查询出将要到期的任务,再执行任务相关逻辑。该方案的优点是实现简单,尤其适合单机或者业务量比较小的场景来。但是缺点也很明显:在分布式且业务量较大的场景中会引入很多复杂性。首先,需要设计一套合理的分库分表逻辑,以及集群任务负载逻辑。其次,即使做到这些,也会由于某些场景定时任务时间集中在某个时间点,导致集群单节点压力过大。再次,需要合理的预估容量,否则后续线性存储扩容将会非常复杂。

    我们的物流处罚其实就是采用类似的机制去实现扫描的,但是后来因为分库等原因最后还是借用了rocketMq来实现的,与其说借用了rocketMq还不如说借用了rocketMq内部的定时器的实现,在开源的4.x的rocketMq版本中其实本质上还是用定时任务加队列的方式来发现任务是否过期。

    传统的方法一个弊端就在于一般情况下我们按照过期粒度,譬如1分钟、10分钟、1小时、24时小时等粒度组装Timer+队列,然后同时有n个线程扫描各自的队列,然后发现其中过期的进行处理,在大量扫描过程中其实很多任务可能还是没有过期的,也就是说白白进行了扫描。那么时间轮在这方面是不是有了优化呢。

    时间轮方案

        时间轮方案将现实生活中的时钟概念引入到软件设计中,主要思路是定义一个时钟周期(比如时钟的12小时)和步长(比如时钟的一秒走一次),当指针每走一步的时候,会获取当前时钟刻度上挂载的任务并执行,整体结构如图1。

时间轮算法

从上图可以看到,对于时间的计算是交给一个类似时钟的组件来做,而任务是通过一个指针或者引用去关联某个刻度上到期的定时任务,这样就能够将定时任务的存储和时间进行解耦,时钟组件难度不大,以何种方式存储这些任务数据,是时间轮方案的关键。

        我理解时间轮的好处在于如果时间轮的指针指到了对应的格子,那么该格子指向的队列里面的任务就都是过期的,可以减少很多不必要的无意义的扫描,至于为什么后面可以看分析。


kafka的时间轮

kafka-timer结构

说明

    kafka的内部Timer其实是自己实现的一个定时器(其实就是一个时间轮),对外提供两个接口,一个接口是由外部调用添加任务add(TimerTask),一个接口是由外部驱动时间轮轮转(advanceClock),当发现任务过期以后则提交专门的任务线程去执行。时间轮内部的真正细节是下面这个图。


kafka-时间轮结构

说明

    其实Timer内部是有一个个TimingWheel来实现时间轮的,为什么会有多个时间轮呢,其实参考我们的时钟就能理解,我们的时钟有秒针(60s)、分针(60m)、时针(60h)。每走一圈代表的时间含义也不相同,所以就会存在多个时间轮了。

    但是我们看到了上面有一个DelayedQueue这个java集合对象,其实它里面保存了所有的延迟任务,因为DelayedQueue本身内部实现是一个有序的堆,我姑且这么认为,所以每次通过DelayedQueue去获取队首数据就是快要过期的数据。

    在进入kafka时间轮源码分析之前,我们需要提前知道的几个概念:子时间轮,父时间轮,添加任务,消费任务等。


kafka时间轮流转

    kafka时间轮的流转其实按照我们上面分析其实分为两个核心步骤,步骤一是任务添加过程,步骤二是执行过期任务。

    任务添加过程

        我们用数组模拟时间轮(数组的每个元素是一个列表头,添加任务就是往列表头后面挂任务而已),数组的大小代表时间的格子数,添加过程中我们会通过 过期时间/时间轮格子代表时间 % 时间轮格子总数 算出的格子位置,然后通过挂链的方法添加到时间轮格子当中。

        在这个过程中我们需要注意的是任务首先需要判断当前时间轮是否放的下,判断放得下的标准就是时间轮当前时间 + 一圈时间轮时间是否大于任务过期时间,如果大于就代表放的下,如果小于就代表无法放置那么就需要往上一层时间轮放置。

        所有时间轮格子其实是放置在一个DelayQueue当中的。

        整个逻辑过程的核心在于hash找时间轮格子的过程,具体可以看下面的源码。


    任务消费过程

        每隔200ms去DelayQueue中以200ms的超时去获取任务(这个过后在末尾的参考文章讲解的很详细),如果获取到说明刚好有一堆超时任务需要处理,那么我们就将所有的任务直接投递到过期任务处理的线程池当中。

       然后将时间轮的格子往前挪一步,挪一步的意思代表时间往前走了一步,然后我们更新当前时间轮的时间,这个时间哪里来的呢,时间就是刚刚我们处理的任务的过期时间。其实这个操作本质上是更新时间轮的当前时间,譬如原理时间是10:00,然后我们处理完一个到期待执行的任务后时间变成了10.40,这个10.40的时间就是代表了过期时间。


kafka时间轮源码

kafka时间轮

说明:

    1、kafka时间轮的核心组成部分包括tickMs(时间格代表时间)、wheelSize(时间轮格子的数量)、startMs(时间轮开始时间)、taskCounter(任务个数)、delayQueue(延迟队列)。

    2、我们每次通过从delayQueue中获取过期任务,如果能够获取到过期任务说明时间轮往前进一格。


kafka时间轮添加任务

说明:

    1、前面提到过我们在添加任务失败就开始执行任务,那么添加任务失败实际代表的是任务已经到期了,对于添加任务其实是分几种情况进行解释的。

    2、如果 任务的过期时间 < 当前时间+单个时间格时间,那么我们任务该任务需要立刻执行。

    3、如果 当前时间+单个时间格时间 <= 任务过期时间 < 当前时间+整个时间轮时间,那么我们首先通过 任务过期时间/时间格时间 代表应该落在具体的哪个格子,但是因为时间轮一直在转动,所以我们需要通过hash来确认应该放在时间轮的哪个位置,最后我们需要设置最新的过期时间并把任务加入到delayedQueue当中,设置的过期时间是通过时间格代表的时间进行的归一。

    4、这里有个疑问就是通过hash的方法计算落在具体的哪个时间格里面,会不会出现覆盖的情况呢,假设我们的时间轮有20个格子,那么20%20=0,40%20=0,60%20=0,岂不是还是会存在落在同一个格子里面但是过期时间不一样的情况嘛,其实不会的,为什么呢,因为我们在前面前置了时间,也就是说基于当前时间我只能放置一个时间轮周期的任务,超过一个时间轮周期的任务我们就会放置大父亲时间轮当中。


父时间轮

说明:

    1、其实父时间轮本质上其时间格代表的时间是子时间轮一周代表的时间而已。


时间轮数据存储结构

说明:

    1、其实时间轮我们是用数组也就是buckets来实现的,也就是说一个数组代表时间轮。

    2、时间轮中每个格子用来保存任务的数据结构是TimeTaskList的数据结构,其实就是一个双向链表,然后每次在往某个时间轮格子里面放置任务也就是timerTaskEntry的时候就会挂置到TimeTaskList的这个对象当中去。

    3、所以说我们放置到DelayedQueue当中其实是TimeTaskList对象,这个对象包含了同一过期时间的所有任务而已。减少了DelayedQueue的大小。


TimerTaskList对象

说明:

    其实TimerTaskList对象就是一个双向列表而已。


TimerTaskEntry对象


时间格移动概念



参考文献

Kafka源码深度解析-序列13 -Server核心组件之2(续)- TimingWheel本质与DelayedOperationPurgatory核心结构

Kafka技术内幕样章 层级时间轮

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,290评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,107评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,872评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,415评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,453评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,784评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,927评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,691评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,137评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,472评论 2 326
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,622评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,289评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,887评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,741评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,316评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,490评论 2 348

推荐阅读更多精彩内容