时间轮算法理解、Kafka实现

概述

TimingWheel,时间轮,简单理解就是一种用来存储若干个定时任务的环状队列(或数组),工作原理和钟表的表盘类似。

关于环形队列,请参考环形队列。

时间轮由两个部分组成,一个环状数组,一个遍历环状数组的指针。

首先定义一个固定长度的环状数组,队列中的每一个元素代表一个时间格(可以精确到秒或毫秒。实际场景里,如Java或Linux下的cron定时任务,都是某一秒来触发。在实时处理领域,则一般用毫秒),一个时间格可存放若干个定时任务(真实业务开发场景下,同时触发多个任务),即任务列表。任务列表是一个环形的双向链表,链表中的每一项表示的都是定时任务项,其中封装真正的定时任务。
在这里插入图片描述
时间格代表时间轮的基本时间跨度或精度,假如一秒走一个时间格的话,则这个时间轮的精度就是1秒。当指针指向某个数组时,就会把这个数组中存储的任务取出来,然后遍历链表逐个运行里面的任务。

下图是一个有12个时间格的时间轮,转完一圈需要12s。当需要新建一个3s后执行的定时任务,只需要将定时任务放在下标为3的时间格中即可。
在这里插入图片描述
当需要创建一个15s后执行的定时任务怎么办呢?

此时可考虑引入圈数(也叫轮数)这一概念,即这个任务还是放在下标为3的时间格中,圈数为2。除增加圈数这种方法之外,还有种多层次时间轮,Kafka采用的就是这种方案。

时间轮的好处:

  • 减少定时任务添加和删除的时间复杂度,提升性能;
  • 可保证每次执行定时器任务都是O(1)复杂度,在定时器任务密集的情况下,性能优势非常明显

实现

在很多开源组件里可看到时间轮算法的实现:Kafka、Netty、Dubbo、Caffeine。

值得一提的是,网络上好多文章说ZooKeeper里也有时间轮算法的实现,并没有。

Kafka

Kafka中有很多延时操作,如耗时的网络请求(如Produce时等待ISR副本复制成功)会被封装成DelayOperation进行延迟处理操作,防止阻塞Kafka请求处理线程。

Kafka没有使用JDK自带的Timer和DelayQueue实现。底层都是个优先队列,即采用minHeap的数据结构,最快需要执行的任务排在队列第一个,不同的是Timer中有个线程去拉取任务执行,DelayQueue是个容器,需要配合其他线程工作。时间复杂度上这两者插入和删除操作都是O(logn),不满足性能要求。

ScheduledThreadPoolExecutor是JDK提供定时线程池,也是DelayQueue + 池化线程的一个实现。

Kafka基于时间轮实现延时操作,时间轮算法的插入删除操作的时间复杂度都是O(1),满足性能要求。

源码类为org.apache.kafka.server.util.timer.TimingWheel

public class TimingWheel {private final long tickMs;private final long startMs;private final int wheelSize;private final AtomicInteger taskCounter;private final DelayQueue<TimerTaskList> queue;private final long interval;private final TimerTaskList[] buckets;private long currentTimeMs;private volatile TimingWheel overflowWheel = null;
}

几个核心参数:

  • tickMs:时间跨度
  • startMs:开始时间
  • wheelSize:时间轮中bucket的个数
  • interval:时间轮的整体时间跨度 = tickMs * wheelSize
  • currentTimeMs:tickMs的整数倍,代表时间轮当前所处的时间。currentTimeMs可以将整个时间轮划分为到期部分和未到期部分,currentTimeMs当前指向的时间格也属于到期部分,表示刚好到期,需要处理此时间格所对应的TimerTaskList中的所有任务

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

Kafka采用多层次时间轮来支持大跨度的定时任务,参考手表。
在这里插入图片描述
上图时间轮,第1层的时间精度为1,第2层的时间精度为20,第3层的时间精度为400。假如需要添加一个350s后执行的任务A的话(当前时间是0s),这个任务会被放在第2层(第二层的时间跨度为20*20=400>350)的第350/20=17个时间格子。

当第一层转17圈之后,时间过去340s,第2层的指针此时来到第17个时间格子。此时第2层第17个格子的任务会被移动到第1层。任务A当前是10s之后执行,因此它会被移动到第1层的第10个时间格子。

在层与层之间的移动,叫做时间轮的升降级。时间轮比较适合任务数量比较多的定时任务场景,它的任务写入和执行的时间复杂度都是O(1)

随着时间推进,也会有一个时间轮降级的操作,原本延时较长的任务会从高一层时间轮重新提交到时间轮中,然后会被放在合适的低层次的时间轮当中等待处理。

在Kafka中时间轮之间如何关联呢,如何展现这种高一层的时间轮关系?
一个内部对象的指针,指向自己高一层的时间轮对象。

如何推进时间轮的前进,让时间轮的时间往前走?
通过DelayQueue来推进,是一种空间换时间的思想;DelayQueue中保存着所有的TimerTaskList对象,根据时间来排序,这样延时越小的任务排在越前面。外部通过一个ExpiredOperationReaper线程从DelayQueue中获取超时的任务列表TimerTaskList,然后根据TimerTaskList的过期时间来精确推进时间轮的时间,这样就不会存在空推进的问题。

Kafka采用权衡的策略,把DelayQueue用在合适地方。DelayQueue只存放TimerTaskList,并不是所有的TimerTask,数量并不多,相比空推进带来的影响是利大于弊的。

总结

  • Kafka使用时间轮来实现延时队列,因为其底层是任务的添加和删除是基于链表实现的,时间复杂度为O(1),满足高性能的要求;
  • 对于时间跨度大的延时任务,引入层级时间轮,能更好控制时间粒度,可以应对更加复杂的定时任务处理场景;
  • 对于如何实现时间轮的推进和避免空推进影响性能,采用空间换时间的思想,通过DelayQueue来推进时间轮。

Netty

io.netty.util.HashedWheelTimer

Netty中的时间轮是通过工作线程按照固定的时间间隔tickDuration推进的,如果长时间没有到期任务,这种方案会带来空推进的问题,造成一定性能损耗;

Dubbo

org.apache.dubbo.common.timer.HashedWheelTimer,和Netty的源码实现几乎一样。

Caffeine

com.github.benmanes.caffeine.cache.TimerWheel

内部类Sentinel代表当前任务,两个内部类AscendingIterator和DescendingIterator分别表示从时间轮取任务的两个方式,

参考

  • Kafka时间轮算法设计
  • HashedWheelTimer使用及源码分析
  • 一个开源的时间轮算法介绍

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

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

相关文章

一文了解MySQL的表级锁

文章目录 ☃️概述☃️表级锁❄️❄️介绍❄️❄️表锁❄️❄️元数据锁❄️❄️意向锁⛷️⛷️⛷️ 介绍 ☃️概述 锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中&#xff0c;除传统的计算资源&#xff08;CPU、RAM、I/O&#xff09;的争用以外&#xff0…

Coze:如何使用团队空间?

你好&#xff0c;我是三桥君 团队空间&#xff0c;是一个允许我们组建团队并共享机器人、插件等资源的功能。 好的&#xff0c;让我们开始创建一个团队。我们将这个团队命名为“三桥君AI”&#xff0c;并在描述中也填写“这里是关于“三桥君AI”团队的描述”。点击确定后&…

VMware_centos8安装

目录 VMware Workstation Pro的安装 安装centos VMware Workstation Pro的安装 正版VMware 17百度网盘下载链接 (含秘钥) 链接&#xff1a;https://pan.baidu.com/s/16zB-7IAACM_1hwR1nsk12g?pwd1111 提取码&#xff1a;1111 第一次运行会要求输入秘钥 秘钥在上边的百度网盘…

【CUDA|CUDNN】安装

every blog every motto: You can do more than you think. https://blog.csdn.net/weixin_39190382?typeblog 0. 前言 显卡驱动安装参考之前的文章 cuda、cudnn 安装 1. cuda 安装 访问https://developer.nvidia.com/cuda-toolkit-archive 选择需要的版本&#xff1a;h…

Selenium使用注意事项:

find_element 和 find_elements 的区别 WebDriver和WebElement的区别 问题&#xff1a; 会遇到报错&#xff1a; selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {"method":"css selector",&…

双管正激小功率电源的设计与实现

正激变换由于拓扑简单&#xff0c; 升/ 降压范围宽&#xff0c; 广泛应用于中小功率电源变换场合。正激变换器的输出功率不象反激变换器那样受变压器储能的限制&#xff0c; 因此输出功率较反激变换器大&#xff0c; 但是正激变换器的开关管电压应力高&#xff0c; 为两倍输入电…

视频监控汇聚平台:通过SDK接入大华DSS视频监控平台的源代码解释和分享

目录 一、视频监控汇聚平台 1、概述 2、视频接入能力 3、视频汇聚能力 二、大华DSS平台 1、DSS平台概述 2、大华DSS平台的主要特点 &#xff08;1&#xff09;高可用性 &#xff08;2&#xff09;高可靠性 &#xff08;3&#xff09;易维护性 &#xff08;4&#xf…

【Mongodb-04】Mongodb聚合管道操作基本功能

Mongodb系列整体栏目 内容链接地址【一】Mongodb亿级数据性能测试和压测https://zhenghuisheng.blog.csdn.net/article/details/139505973【二】springboot整合Mongodb(详解)https://zhenghuisheng.blog.csdn.net/article/details/139704356【三】亿级数据从mysql迁移到mongodb…

SpringCloud第三篇(服务中心与OpenFeign)

p 文章目录 一、服务中心二、Nacos注册中心 一、服务中心 在上一章我们实现了微服务拆分&#xff0c;并且通过Http请求实现了跨微服务的远程调用。不过这种手动发送Http请求的方式存在一些问题。 试想一下&#xff0c;假如商品微服务被调用较多&#xff0c;为了应对更高的并发…

【产品应用】一体化步进伺服电机在板材封边机中的应用

随着现代工业技术的快速发展&#xff0c;封边机作为木材加工行业的重要设备&#xff0c;其精度、效率和稳定性已成为衡量设备性能的重要指标。 近年来&#xff0c;一体化步进伺服电机因其高精度、高效率和强稳定性等特点&#xff0c;在封边机中得到了广泛应用。 本文将详细介绍…

1.5.1抽象java入门

前言&#xff1a; 1.5.0版本中&#xff0c;我们熟练使用Git三个可视化操作&#xff08;签出&#xff0c;提交&#xff0c;对比&#xff09;&#xff0c;再加上1.4.0版本的新建&#xff0c;总计使用四个Git可视化操作&#xff1b;对java编程的学习&#xff0c;总结&#xff0c;…

vue 前端项目调用后端接口记录

axios中不同的类型的请求附带数据使用的关键字 请求类型关键字示例GETparamsaxios({ method: get, url: example.com, params: { key: value } })POSTdataaxios({ method: post, url: example.com, data: { key: value } })PUTdataaxios({ method: put, url: example.com, dat…

MICCAI 2024 每日一篇论文 纯纯直读 CUTS:用于多粒度无监督医学图像分割的深度学习和拓扑框架

MICCAI 2024 CUTS: A Deep Learning and Topological Framework for Multigranular Unsupervised Medical Image Segmentation CUTS: 用于多粒度无监督医学图像分割的深度学习和拓扑框架 作者 陈璐1*、Matthew Amodio1*、梁博伦.沈2、冯高3、阿曼阿维斯塔4、Sanjay Aneja3,5…

[RuoYi-Vue] - 1. 项目搭建

文章目录 &#x1f42c;初始化后端项目拉取RuoYi-Vue代码Maven构建导入数据库ry-vue修改配置信息启动Redis启动项目 &#x1f30c;初始化前端项目拉取RuoYi-Vue3代码项目运行成功页面 &#x1f42c;初始化后端项目 拉取RuoYi-Vue代码 若依/RuoYi-Vue 代码地址 Maven构建 导入数…

7.2 AQS原理

AQS 原理 概述 全称是 AbstractQueuedSynchronizer&#xff0c;是阻塞式锁和相关的同步器工具的框架。 特点&#xff1a; 用 state 属性来表示资源的状态&#xff08;分独占模式和共享模式&#xff09;&#xff0c;子类需要定义如何维护这个状态&#xff0c;控制如何获取锁和…

three.js官方案例(animation / skinning / ik)webgl_animation_skinning_ik.html学习记录

目录 1 WebGLCubeRenderTarget 2 TransformControls 3 CCDIKSolver 4 CCDIKHelper 4 全部脚本 1 WebGLCubeRenderTarget 球体亮 //WebGLCubeRenderTarget(size : Number, options : Object) //size - the size, in pixels. Default is 1. //options - (可选)一个保存…

软件设计之Java入门视频(15)

软件设计之Java入门视频(15) 视频教程来自B站尚硅谷&#xff1a; 尚硅谷Java入门视频教程&#xff0c;宋红康java基础视频 相关文件资料&#xff08;百度网盘&#xff09; 提取密码&#xff1a;8op3 idea 下载可以关注 软件管家 公众号 学习内容&#xff1a; 该视频共分为1-7…

Linux桌面溯源

X窗口系统(X Window System) Linux起源于X窗口系统&#xff08;X Window System&#xff09;&#xff0c;亦即常说的X11&#xff0c;因其版本止于11之故。 X窗口系统&#xff08;X Window System&#xff0c;也常称为X11或X&#xff09;是一种以位图方式显示的软件窗口系统。…

zabbix 7.0 SNMP Hex数据预处理新功能

一、简介 zabbix7.0新特性是监控项新增支持SNMP Hex数据预处理。其中内置了对snmp请求结果Hex转换处理&#xff0c;不再需要使用繁琐的方式&#xff0c;如javascript脚本、替换、修整等方式处理将监控项取值做可视化处理&#xff0c;大福提升SNMP采集获取到Hex数据的处理效率。…

浅析 VO、DTO、DO、PO 的概念

文章目录 I 浅析 VO、DTO、DO、PO1.1 概念1.2 模型1.3 VO与DTO的区别I 浅析 VO、DTO、DO、PO 1.1 概念 VO(View Object) 视图对象,用于展示层,它的作用是把某个指定页面(或组件)的所有数据封装起来。DTO(Data Transfer Object): 数据传输对象,这个概念来源于J2EE的设…