时间轮算法理解、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使用及源码分析
  • 一个开源的时间轮算法介绍

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

韦东山嵌入式linux系列-驱动设计的思想(面向对象/分层/分离)

1 面向对象 字符设备驱动程序抽象出一个 file_operations 结构体&#xff1b; 我们写的程序针对硬件部分抽象出 led_operations 结构体。 2 分层 上下分层&#xff0c;比如我们前面写的 LED 驱动程序就分为 2 层&#xff1a; ① 上层实现硬件无关的操作&#xff0c;比如注册…

WPF学习(3) -- 控件模板

一、操作过程 二、代码 <Window x:Class"学习.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d"http://schemas.microsoft.com/expressio…

css基础(1)

CSS CCS Syntax CSS 规则由选择器和声明块组成。 CSS选择器 CSS选择器用于查找想要设置样式的HTML元素 一般选择器分为五类 Simple selectors (select elements based on name, id, class) 简单选择器&#xff08;根据名称、id、类选择元素&#xff09; //页面上的所有 …

WEB前端03-CSS3基础

CSS3基础 1.CSS基本概念 CSS是Cascading Style Sheets&#xff08;层叠样式表&#xff09;的缩写&#xff0c;它是一种对Web文档添加样式的简单机制&#xff0c;是一种表现HTML或XML等文件外观样式的计算机语言&#xff0c;是一种网页排版和布局设计的技术。 CSS的特点 纯C…

Oracle使用fetch first子句报错:ORA-00933 SQL命令未正确结束

问题背景 今天在统计终端厂商告警次数Top10的时候使用SQL查询使用到了fetch first子句&#xff0c;结果执行报错&#xff1a;ORA-00933 SQL命令未正确结束。 报错原因 Oracle数据库中&#xff0c;使用 FETCH FIRST 子句需要启用 Oracle 12c 及以上版本。如果在较低版本的 Or…

基于神经网络的分类和预测

基于神经网络的分类和预测 一、基础知识&#xff08;一&#xff09;引言&#xff08;二&#xff09;神经网络的基本概念&#xff08;1&#xff09;神经网络&#xff08;2&#xff09;神经元&#xff08;3&#xff09;常用的激活函数&#xff08;非线性映射函数&#xff09;&…

ISO 45001:提升职业健康与安全管理水平的关键

在现代企业管理中&#xff0c;员工的职业健康与安全&#xff08;OH&S&#xff09;已经成为不可忽视的重要议题。ISO 45001作为国际标准化组织&#xff08;ISO&#xff09;制定的职业健康与安全管理体系标准&#xff0c;为企业提供了科学有效的管理规范和指南。实施这一标准…

Go-知识测试-子测试

Go-知识测试-子测试 1. 介绍2. 例子3. 子测试命名规则4. 选择性执行5. 子测试并发6. testing.T.Run7. testing.T.Parallel8. 子测试适用于单元测试9. 子测试适用于性能测试10. 总结10.1 启动子测试 Run10.2 启动并发测试 Parallel 建议先看&#xff1a;https://blog.csdn.net/a…

探索“搭旅万物皆可搭”小程序——构建旅行搭伴平台的创新实践

摘要 随着旅游市场的不断发展和个性化需求的日益增长&#xff0c;旅行搭伴平台逐渐成为连接志同道合旅者的桥梁。本文旨在介绍“搭旅万物皆可搭”小程序的设计理念、核心功能及其背后的技术实现&#xff0c;探讨如何通过算法优化、安全保障、社交互动等手段&#xff0c;打造一…

手撕Vue中的RouterLink和RouterView,深入理解其底层原理(一)

RouterLink和RouterView的作用 我们可以通过RouterLink绑定好指向的路径 点击就能够实现在RouterView中将页面显示出来 我们首先使用官方的vue-router展示一下效果 App.vue <template><div><router-link to"/">Home</router-link><ro…

51单片机STC89C52RC——17.2 红外遥控数字加减、电机调速

目的/效果 1&#xff1a;按VOL-键数字减、按VOL加数字加 2&#xff1a;按键 0&#xff0c;1&#xff0c;2&#xff0c;3&#xff0c;4 电机调速 一&#xff0c;STC单片机模块 二&#xff0c;红外遥控 详细了解红外遥控控制原理请参考《51单片机STC89C52RC——17.1 红外线遥控…

UE4 解决创建布料报错:三角形退化

**【问题】**创建创建布料时报错&#xff1a;三角形退化 【方法】 1.要重新绑定&#xff1a;导入到ue4为静态网格体&#xff0c;勾选“移除退化”&#xff0c;再导出fbx&#xff0c;再重新绑定 2.不用重新绑定&#xff1a;使用排除法&#xff08;费时&#xff09;&#xff0c…

Spring Boot快速上手

一&#xff0c;什么是spring 首先登陆Spring官网&#xff0c;看一下官网如何形容的&#xff0c; 可以看出Spring是为了使java程序更加快速&#xff0c;方便&#xff0c;安全所做出的java框架。 1.Spring Boot Spring Boot的诞生就是为了简化Spring的开发&#xff0c;也就是更…

【Quart 框架——来源于Flask的强大且灵活的异步Web框架】

目录 前言一、Quart简介1-1、简介1-2、与flask的区别 二、快速开始2-1、安装2-2、基本用法 三、核心功能3-1、异步路由3-2、WebSockets 支持3-3、中间件3-4、蓝图 (Blueprints) 四、部署4-1、使用uvicorn部署4-2、使用hypercorn部署 五、案例分析总结 前言 Quart 是一个基于 Py…

taocms 3.0.1 本地文件泄露漏洞(CVE-2021-44983)

前言 CVE-2021-44983 是一个影响 taoCMS 3.0.1 的远程代码执行&#xff08;RCE&#xff09;漏洞。该漏洞允许攻击者通过上传恶意文件并在服务器上执行任意代码来利用这一安全缺陷。 漏洞描述 taoCMS 是一个内容管理系统&#xff08;CMS&#xff09;&#xff0c;用于创建和管…

用Qwt进行图表和数据可视化开发

目录 Qwt介绍 示例应用场景 典型QWT开发流程 举一些Qwt的例子&#xff0c;多绘制几种类型的图像 1. 绘制折线图 (Line Plot) 2. 绘制散点图 (Scatter Plot) 3. 绘制柱状图 (Bar Plot) 4. 绘制直方图 (Histogram) Qwt介绍 QWT开发主要涉及使用QWT库进行图表和数据可视化…

在若依框架基础上开发新功能

本文介绍如何在若依框架&#xff08;不分离版本&#xff09;的基础上开发新功能。 目录 运行若依框架 下载若依框架代码 IDEA打开若依框架代码 初始化数据库 修改数据库配置 运行项目 设计数据库 数据表命名规则 建表及初始化数据 开发新功能 后端CRUD功能 用户前端…

从零开始做题:神奇的棋盘

题目 打开得到一副adfgvx加密棋盘 观察txt数据只有1-5&#xff0c;猜测是数字字母坐标转换&#xff0c;用notepad批量操作一下 解题 AGAXXDAGGVGGVDVADAVXDGADVGDVAADDDDFXAFAFDGDVXXDGGDGGDXDDFDDXVGXADGVDFXVVAADDXDXXADDVGGGXGXXXXGXXGGXGDVVVGGGAGAAAAGAAGGAGDDDAGAGGG…

JS实现:统计字符出现频率/计算文字在文本中的出现次数

要实现这个功能&#xff0c;JavaScript 一个非常强大的方法&#xff0c;那就是reduce() reduce() 它用于将数组的所有元素减少到一个单一的值。这个值可以是任何类型&#xff0c;包括但不限于数字、字符串、对象或数组。 reduce() 方法接收一个回调函数作为参数&#xff0c;这个…

Java单边表的局部翻转

反转链表 II 这是上一个翻转全部链表的进阶版&#xff0c;大家可以先去看我的上一篇博客 Java算法之单链表的全部翻转-CSDN博客 题目描述 给你单链表的头指针 head 和两个整数 left 和 right &#xff0c;其中 left < right 。请你反转从位置 left 到位置 right 的链表节…