透彻理解Kafka(十)——时间轮调度

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

学习必须往深处挖,挖的越深,基础越扎实!

阶段1、深入多线程

阶段2、深入多线程设计模式

阶段3、深入juc源码解析


阶段4、深入jdk其余源码解析


阶段5、深入jvm源码解析

码哥源码部分

码哥讲源码-原理源码篇【2024年最新大厂关于线程池使用的场景题】

码哥讲源码【炸雷啦!炸雷啦!黄光头他终于跑路啦!】

码哥讲源码-【jvm课程前置知识及c/c++调试环境搭建】

​​​​​​码哥讲源码-原理源码篇【揭秘join方法的唤醒本质上决定于jvm的底层析构函数】

码哥源码-原理源码篇【Doug Lea为什么要将成员变量赋值给局部变量后再操作?】

码哥讲源码【你水不是你的错,但是你胡说八道就是你不对了!】

码哥讲源码【谁再说Spring不支持多线程事务,你给我抽他!】

终结B站没人能讲清楚红黑树的历史,不服等你来踢馆!

打脸系列【020-3小时讲解MESI协议和volatile之间的关系,那些将x86下的验证结果当作最终结果的水货们请闭嘴】

Kafka中存在大量的延时操作,比如延时生产、延时拉取和延时删除等。Kafka并没有使用JDK 自带的 Timer 或 DelayQueue 来实现延时的功能,而是基于 时间轮 的概念实现了一个用于延时功能的定时器( SystemTimer )。

JDK 中 Timer 和 DelayQueue 的插入和删除操作的平均时间复杂度为 *O(nlogn)*并不能满足 Kafka 的高性能要求,而基于时间轮的插入和删除操作的时间复杂度为 0(1) 。时间轮的设计思想在很多开源框架中都有应用,比如Netty 、ZooKeeper等等。

一、设计思想

1.1 基本结构

Kafka 中的时间轮( TimingWheel )是一个存储定时任务的 环形队列 , 底层采用数组实现,数组中的每个元素可以存放一个定时任务列表( TimerTaskList )。 TimerTaskList是一个环形的 双向链表 ,链表中的每一项就是定时任务项( TimerTaskEntry ),其中封装了真正的定时任务( TimerTask )。

如上图,时间轮由多个时间格组成, 每个时间格就是基本时间跨度tickMs 。时间轮的格数是固定的,用wheelSize表示,那么整个时间轮的总体时间跨度interval = tickMs * wheelSize

举个例子:若时间轮的tickMs = 1ms ,wheelSize = 20 ,那么总体时间跨度interval就是20ms,可以用来存放延时时间在0-20ms内的定时任务。

  1. 初始情况下,表盘指针 currentTime 指向时间格0;
  2. 有一个定时为 2ms 的任务插进来会存放到时间格为2的TimerTaskList 中;
  3. 随着时间推移 , 指针 currentTime 不断向前推进,过了 2ms 之后,当到达时间格 2 时,就需要将时间格 2 对应的 TimeTaskList 中的任务进行相应的到期操作;
  4. 此时若又有一个定时为 8ms 的任务插进来,则会存放到时间格 10 中,currentTime 再过 8ms 后会指向时间格 10 ;
  5. 如果同时有一个定时为 19ms 的任务插进来,新来的 TimerTaskEntry 会复用原来的 TimerTaskList,所以它会插入原本己经到期的时间格1 。

以上就是整个时间轮的运行机制,总之,整个时间轮的总体跨度是不变的,随着指针 currentTime的不断推进,当前时间轮所能处理的时间段也在不断后移,总体时间范围在 currentTime 和 currentTime+interval 之间 。

1.2 多层级

那么这里有个问题,若整个时间轮的总体时间跨度interval = tickMs * wheelSize,比如20ms,那么对于定时为350ms 的任务该如何处理?此时已经超出了时间轮能表示的时间跨度。

Kafka 为此引入了 层级时间轮 的概念,当任务的到期时间超过了当前时间轮所表示的时间范围时,就会尝试添加到上层时间轮中。比如对于20ms跨度的时间轮,它的上级是interval = 20 * 20 = 400ms,对于400ms跨度的时间轮,它的上级是interval = 400 * 20 = 8000ms,以此类推:

举个例子,对于450ms 的定时任务:

  1. 首先,会升级存放到第三层时间轮中,被插入到第三层时间轮的时间格1所对应的 TimerTaskList;
  2. 随着时间的流逝,当此 TimerTaskList 到期之时,原本定时为 450ms 的任务还剩下 50ms 的时间,还不能执行这个任务的到期操作;
  3. 于是执行 时间轮降级 ,将剩余时间为 50ms 的定时任务重新提交到第二层到期时间为 [40ms,60ms)的时间格中;
  4. 再经历 40ms 之后,此时这个任务又被 “察觉 ”,不过还剩余 10ms ,所以还要降级一次,放到第一层时间轮的[ 10ms, 11ms)的时间格中;
  5. 最后,经历 l0ms 后,此任务真正到期,最终执行相应的到期操作。

1.3 DelayQueue

Kafka 使用了 JDK 中的 DelayQueue 来推进时间轮。具体做法是将每个使用到的 TimerTaskList 都加入到一个DelayQueue中,DelayQueue 会根据 TimerTaskList的超时时间来排序,最短超时时间的TimerTaskList会被排在 DelayQueue 的队头。

然后,Kafka 中会有一个线程ExpiredOperationReaper来获取DelayQueue中到期的任务列表,这样这个线程既可以对任务进行时间轮降级,也可以直接执行这个任务。

总结一下,Kafka 中的时间轮( TimingWheel )专门用来插入和删除延时任务,而DelayQueue则专门负责时间推进。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值