1-kafka服务端之延时操作前传--时间轮

文章目录

  • 背景
  • 时间轮
  • 层级时间轮
  • 时间轮降级
  • kafka中的时间轮
  • kafka如何进行时间轮运行

背景

Kafka中存在大量的延时操作,比如延时生产、延时拉取和延时删除等。Kafka并没有使用JDK自带的Timer或DelayQueue来实现延时的功能,而是基于时间轮的概念自定义实现了一个用于延时功能的定时器(SystemTimer)。JDK中Timer和DelayQueue的插入和删除操作的平均时间复杂度为O(nlogn)并不能满足Kafka的高性能要求,而基于时间轮可以将插入和删除操作的时间复杂度都降为O(1)。时间轮的应用并非Kafka独有,其应用场景还有很多,在Netty Akka、Quartz、ZooKeeper等组件中都存在时间轮的踪影。

时间轮

如图所示,Kafka中的时间轮(Timingwheel)是一个存储定时任务的环形队列,底层采用数组实现,数组中的每个元素可以存放一个定时任务列表(TimerTaskList)。TimerTaskList是一个环形的双向链表,链表中的每一项表示的都是定时任务项(TimerTaskEntry),其中封装了真正的定时任务(TimerTask)。
在这里插入图片描述

时间轮由多个时间格组成,每个时间格代表当前时间轮的基本时间跨度(tickMs)。时间轮的时间格个数是固定的,可用wheelSize来表示,那么整个时间轮的总体时间跨度(interval)可以通过公式tickMs×wheelSize计算得出。时间轮还有一个表盘指针(currentTime),用来表示时间轮当前所处的时间,currentTime是tickMs的整数倍。currentTime可以将整个时间轮划分为到期部分和未到期部分,currentTime当前指向的时间格也属于到期部分,表示刚好到期,需要处理此时间格所对应的TimerTaskList中的所有任务。

若时间轮的tickMs为1ms且wheelSize等于20,那么可以计算得出总体时间跨度interval为20ms。初始情况下表盘指针currentTime指向时间格0,此时有一个定时为2ms的任务插进来会存放到时间格为2的TimerTaskList中。随着时间的不断推移,指针currentTme不断向前推进,过了2ms之后,当到达时间格2时,就需要将时间格2对应的TimeTaskList中的任务进行相应的到期操作。此时若又有一个定时为8ms的任务插进来,则会存放到时间格10中,currentTime再过8ms后会指向时间格10。如果同时有一个定时为19ms的任务插进来怎么办?

新来的TimerTaskEntry会复用原来的TimerTaskList,所以它会插入原本已经到期的时间格1。总之,整个时间轮的总体跨度是不变的,随着指针currentTime的不断推进,当前时间轮所能处理的时间段也在不断后移,总体时间范围在currenttTime 和 currentTime+interval 之间。

层级时间轮

如果此时有一个定时为350ms的任务该如何处理?直接扩充wheelSize的大小?Kafka中不乏几万甚至几十万毫秒的定时任务,这个wheelSize的扩充没有底线,就算将所有的定时任务的到期时间都设定一个上限,比如100万毫秒,那么这个wheelSize为100万毫秒的时间轮不仅占用很大的内存空间,而且也会拉低效率。Kafka为此引入了层级时间轮的概念,当任务的到期时间超过了当前时间轮所表示的时间范围时,就会尝试添加到上层时间轮中。

如图所示,复用之前的案例,第一层的时间轮tickMslms、wheelSize=20、interval=20ms。第二层的时间轮的tickMs为第一层时间轮的interval,即20ms。每一层时间轮的wheelSize是固定的,都是20,那么第二层的时间轮的总体时间跨度interval为400ms。以此类推,这个400ms也是第三层的tickMs的大小,第三层的时间轮的总体时间跨度为8000ms。
在这里插入图片描述

时间轮降级

对于之前所说的350ms的定时任务,显然第一层时间轮不能满足条件,所以就升级到第二层时间轮中,最终被插入第二层时间轮中时间格17所对应的TimerTaskList。如果此时又有一个定时为450ms的任务,那么显然第二层时间轮也无法满足条件,所以又升级到第三层时间轮中最终被插入第三层时间轮中时间格1的TimerTaskList。注意到在到期时间为[400ms800ms)区间内的多个任务(比如446ms、455ms和473ms的定时任务)都会被放入第三层时间轮的时间格1,时间格1对应的TimerTaskList的超时时间为400ms。随着时间的流逝,当此TimerTaskList到期之时,原本定时为450ms的任务还剩下50ms的时间,还不能执行这个任务的到期操作。这里就有一个时间轮降级的操作,会将这个剩余时间为50ms的定时任务重新提交到层级时间轮中,此时第一层时间轮的总体时间跨度不够,而第二层足够,所以该任务被放到第二层时间轮到期时间为40ms,60ms)的时间格中再经历40ms之后,此时这个任务又被“察觉”,不过还剩余10ms,还是不能立即执行到期操作。所以还要再有一次时间轮的降级,此任务被添加到第一层时间轮到期时间为[10ms,11ms)的时间格中,之后再经历10ms后,此任务真正到期,最终执行相应的到期操作。

设计源于生活。我们常见的钟表就是一种具有三层结构的时间轮,第一层时间轮
tickMs=lms、wheelSize=60、interval=lmin,此为秒钟:第二层tickMs=lmin、wheelSize-60、interval=lhour,此为分钟;第三层tickMs=lhour、wheelSize-12、interval=l2hours,此为时钟。

kafka中的时间轮

在Kafka中,第一层时间轮的参数同上面的案例一样:ttickMs=lms、wheelSize-20、interval=20ms,各个层级的wheelSize也固定为20,所以各个层级的tickMs和interval也可以相应地推算出来。Kafka在具体实现时间轮Timingwheel时还有一些小细节:

  • TimingWheel在创建的时候以当前系统时间为第一层时间轮的起始时间JCstartMs)这里的当前系统时间并没有简单地调用System.currentTimeMillisO,而是调用了Time.SYSTEM.hiResClockMs,这是因为currentTimeMillisO方法的时间精度依赖于操作系统的具体实现,有些操作系统下并不能达到毫秒级的精度,而Time.SYSTEM.hiResClockMs实质上采用了System.nanoTime(/1000.000来将精度调整到毫秒级。

  • Timingwheel中的每个双向环形链表TimerTaskList都会有一个哨兵节(sentinel),引入哨兵节点可以简化边界条件。哨兵节点也称为哑元节点(dummynode),它是一个附加的链表节点,该节点作为第一个节点,它的值域中并不存储任何东西,只是为了操作的方使而引入的。如果一个链表有哨兵节点,那么线性表的第一个元素应该是链表的第二个节点。(典型的链表数据结构实现)

  • 除了第一层时间轮,其余高层时间轮的起始时间(startMs)都设置为创建此层时间轮时前面第一轮的currentTime。每一层的currentTime都必须是tickMs的整数倍,如果不满足则会将currentTime修剪为tickMs的整数倍,以此与时间轮中的时间格的到期时间范围对应起来。修剪方法为:currentTimestartMs= startMs -(startMs % tickMs)。 currentTime会随着时间推移而推进,但不会改变为tickMs的整数倍的既定事实。若某一时刻的时间为timeMs,那么此时时间轮的currentTime
    =timeMs-(timeMs%tickMs),时间每推进一次,每个层级的时间轮的currentTime都会依据此公式执行推进。

  • Kafka中的定时器只需持有Timingwheel的第一层时间轮的引用,并不会直接持有其他高层的时间轮,但每一层时间轮都会有一个引用(overfowwheel)指向更高一层的应用,以此层级调用可以实现定时器间接持有各个层级时间轮的引用。

kafka如何进行时间轮运行

上面我们说过“随着时间的流逝”或“随着时间的推移”,那么在Kafka中到底是怎么推进时间的呢?类似采用JDK中的scheduleAtFixedRate来每秒推进时间轮?显然这样并不合理,Timingwheel1也失去了大部分意义。

Kafka中的定时器借了JDK中的DelayQueue来协助推进时间轮。具体做法是对于每个使用到的TimerTaskList者都加入DelayQueue,每个用到的TimerTaskList”特指非哨兵节点的定时任务项TimerTaskEntry对应的TimerTaskListoDelayQueue会根据TimerTaskList对应的超时时间expiration来排序,最短expiration的TimerTaskList会被排在DelayOueue的队头。Kafka中会有一个线程来获取DelayQueue中到期的任务列表,有意思的是这个线程所对应的名称叫作“ExpiredOperationReaper”,可以直译为“过期操作收割机”。当“收割机”线程获取DelayQueue中超时的任务列表TimerTaskList之后,既可以根据TimerTaskList的expiration来推进时间轮的时间,也可以就获取的TimerTaskList执行相应的操作,对里面的TimerTaskEntry该执行过期操作的就执行过期操作,该降级时间轮的就降级时间轮。

看到这里或许会感到困惑,开头明确指明的DelayQueue不适合Kafka这种高性能要求的定时任务,为何这里还要引入DelayQueue呢?注意对定时任务项TimerTaskEntry的插入和删除操作而言,Timingwheel时间复杂度为0(I),性能高出DelayQueue很多,如果直接将TimerTaskEntry插入DelayQueue,那么性能显然难以支撑。就算我们根据一定的规则将若干TimerTaskEntry划分到TimerTaskList这个组中,然后将TimerTaskList插入DelayQueue,如果在TimerTaskList中又要多添加一个TimerTaskEntry时该如何处理呢?对DelayQueue而言,这类操作显然变得力不从心。

到这里可以发现,Kafka中的Timingwheel专门用来执行插入和删除TimerTaskEntry的操作,而DelayQueue专门负责时间推进的任务。试想一下,DelayQueuee中的第一个超时任务列表的expiration为200ms,第二个超时任务为840ms,这里获取DelayQueue的队头只需要O(1)的时间复杂度(获取之后DelayQueue内部才会再次切换出新的队头)。如果采用每秒定时推进,那么获取第一个超时的任务列表时执行的200次推进中有199次属于“空推进”,而获取第二个超时任务时又需要执行639次“空推进”,这样会无故空耗机器的性能资源,这里采用DelayQueue来辅助以少量空间换时间,从而做到了“精准推进”。Kafka中的定时器真可谓“知人善用”,用Timingwheel做最擅长的任务添加和删除操作,而用DelayQueue做最擅长的时间推进工作,两者相辅相成。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/68130.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从零开始:OpenCV 图像处理快速入门教程

文章大纲 第1章 OpenCV 概述 1.1 OpenCV的模块与功能  1.2 OpenCV的发展 1.3 OpenCV的应用 第2章 基本数据类型 2.1 cv::Vec类 2.2 cv::Point类 2.3 cv::Rng类 2.4 cv::Size类 2.5 cv:&…

网络工程师 (22)网络协议

前言 网络协议是计算机网络中进行数据交换而建立的规则、标准或约定的集合,它规定了通信时信息必须采用的格式和这些格式的意义。 一、基本要素 语法:规定信息格式,包括数据及控制信息的格式、编码及信号电平等。这是协议的基础,确…

算法与数据结构(括号匹配问题)

思路 从题干可以看出,只要给出的括号对应关系正确,那么就可以返回true,否则返回false。这个题可以使用栈来解决 解题过程 首先从第一个字符开始遍历,如果是括号的左边(‘(‘,’[‘,’}‘&…

kaggle比赛入门 - Spaceship Titanic (第一部分)

1. 导入packages import numpy as np import pandas as pd import matplotlib.pyplot as plt %matplotlib inline import seaborn as sns sns.set(styledarkgrid, font_scale1.4) from imblearn.over_sampling import SMOTE import itertools import warnings warnings.filter…

java基础2(黑马)

一、变量里的数据在计算机中的存储原理 1.二进制 .二进制:只有0、1, 按照逢二进一的方式表示数据。 十进制数字11转换为:1011 方法:除二取余法 计算机中表示数据的最小单元,一个字节(Byte,简…

AlwaysOn 可用性组副本所在服务器以及该副本上数据库的各项状态信息

目录标题 语句代码解释:1. sys.dm_hadr_database_replica_states 视图字段详细解释及官网链接官网链接字段解释 2. sys.availability_replicas 视图字段详细解释及官网链接官网链接字段解释 查看视图的创建语句方法一:使用 SQL Server Management Studio…

GPU-Z重磅更新,Blackwell架构全面支持

由TechPowerUp倾力打造的GPU-Z,是一款集显卡信息查看、实时监控与深度诊断于一体的强大工具。它以其轻巧灵便的体积、完全免费的使用模式以及极其友好的操作界面,赢得了全球无数用户的青睐与信任,成为PC硬件领域中不可或缺的软件。 GPU-Z不仅…

程序诗篇里的灵动笔触:指针绘就数据的梦幻蓝图<6>

大家好啊,我是小象٩(๑ω๑)۶ 我的博客:Xiao Xiangζั͡ޓއއ 很高兴见到大家,希望能够和大家一起交流学习,共同进步。 今天我们继续来学习数组指针变量,二维数组传参的本质,函数指针变量,…

MySQL时间类型相关总结(DATETIME, TIMESTAMP, DATE, TIME, YEAR)

MySQL时间类型相关总结(DATETIME, TIMESTAMP, DATE, TIME, YEAR) MySQL官方文档: https://dev.mysql.com/doc/refman/8.0/en/date-and-time-types.html 一. 对比: 在 MySQL 中,处理时间相关的数据类型主要有以下几种:DATE、TIME、…

前缀和练习——洛谷P8218:求区间和

题目: 这道题很简单&#xff0c;直接根据题目无脑套公式 代码&#xff1a; #include<bits/stdc.h> using namespace std; const int N 1e5 9; using ll long long; ll a[N], perfix[N]; int main() {ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);//取消同步输…

【STM32】蓝牙模块数据包解析

使用到的蓝牙模块为DX-BT24&#xff0c;他可以将串口转蓝牙&#xff0c;实现与手机蓝牙的通信&#xff0c;本次实现使用手机蓝牙发送数据包来控制单片机LED的亮灭&#xff0c;规则如下&#xff1a; AA 05 01 FF AF 该数据包表示包头为AA&#xff0c;05表示该数据包的大小&#…

NSS-DAY2

Crypto [HNCTF 2022 Week1]A dictator 题目&#xff1a; from random import randint from secret import flagoffset randint(1,100) % 26 # print(offset)assert flag.startswith(NSSCTF{) assert all([ord(c) not in range(ord(A),ord(Z)) for c in flag[7:-1]])for cha…

【vue3 入门到实战】7. 标签中的 ref

目录 1. ref 的作用 2. 如何使用 1. ref 的作用 用于注册模板引用 用在普通DOM标签上&#xff0c;获取的是DOM节点。 用在组件标签上&#xff0c;获取的是组件的实例对象。 2. 如何使用 代码如下 <template><div class"app"><h2 ref"titl…

手写MVVM框架-实现简单的数据代理

MVVM框架最显著的特点就是虚拟dom和响应式的数据、我们以Vue为例&#xff0c;分别实现data、computed、created、methods以及虚拟dom。 这一章我们先实现简单的响应式&#xff0c;修改数据之后在控制台打印。 我们将该框架命名为MiniVue。 首先我们需要创建MiniVue的类(src/co…

spy-debugger + Charles 调试移动端/内嵌小程序H5

简介说明&#xff1a; PC端可以用F12进行console等进行调试&#xff0c;但移动端App中使用webview就无法进行实时调试&#xff0c;针对这种情况 1. 安装 全局安装 spy-debugger sudo npm install spy-debugger -g // window不用加sudo2. spy-debugger 证书 其实spy-debugg…

【目标检测】模型验证:K-Fold 交叉验证

K-Fold 交叉验证 1、引言1.1 K 折交叉验证概述 2、配置2.1 数据集2.2 安装包 3、 实战3.1 生成物体检测数据集的特征向量3.2 K 折数据集拆分3.3 保存记录3.4 使用 K 折数据分割训练YOLO 4、总结 1、引言 我们将利用YOLO 检测格式和关键的Python 库&#xff08;如 sklearn、pan…

Android studio ternimal 中gradle 指令失效(gradle环境变量未配置)

默认gradle路径&#xff1a;C:\Users\ylwj.gradle\wrapper\dists\gradle-8.10.2-bin\a04bxjujx95o3nb99gddekhwo\gradle-8.10.2\bin 环境变量-系统环境变量-双击path-配置上即可-注意重启studio才会生效

Axure大屏可视化动态交互设计:解锁数据魅力,引领决策新风尚

可视化组件/模板预览&#xff1a;https://8dge09.axshare.com 一、大屏可视化技术概览 在数据驱动决策的时代&#xff0c;大屏可视化技术凭借直观、动态的展示方式&#xff0c;已成为众多行业提升管理效率和优化决策过程的关键工具。它能够将复杂的数据转化为易于理解的图形和…

Resnet 改进:尝试在不同位置加入Transform模块

目录 1. TransformerBlock 2. resnet 3. 替换部分卷积层 4. 在特定位置插入Transformer模块 5. 使用Transformer全局特征提取器 6. 其他 Tips:融入模块后的网络经过测试,可以直接使用,设置好输入和输出的图片维度即可 1. TransformerBlock TransformerBlock是Transfo…

MySQL调优02 - SQL语句的优化

SQL语句的优化 文章目录 SQL语句的优化一&#xff1a;SQL优化的小技巧1&#xff1a;编写SQL时的注意点1.1&#xff1a;查询时尽量不要使用*1.2&#xff1a;连表查询时尽量不要关联太多表1.3&#xff1a;多表查询时一定要以小驱大1.4&#xff1a;like不要使用左模糊或者全模糊1.…