时间轮算法理解、Kafka实现

news2024/11/15 12:08:53

概述

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.coloradmin.cn/o/1924960.html

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

相关文章

企业智能制造赋能的环境条件为什么重要?需要准备什么样的环境?

在全球制造业不断演进的今天&#xff0c;智能制造已经成为推动行业创新和转型的关键力量。它不仅代表了技术的革新&#xff0c;更是企业管理模式和运营思路的全面升级。然而&#xff0c;智能制造的落地实施并非一蹴而就&#xff0c;它需要企业在环境条件上做好充分的准备&#…

Study--Oracle-07-ASM自动存储管理(一)

一、ASM实例和数据库实例对应关系 1、ASM是Oracle 10g R2中为了简化Oracle数据库的管理而推出来的一项新功能&#xff0c;这是Oracle自己提供的卷管理器&#xff0c;主要用于替代操作系统所提供的LVM&#xff0c;它不仅支持单实例&#xff0c;同时对RAC的支持也是非常好。ASM可…

C语言 | Leetcode C语言题解之第231题2的幂

题目&#xff1a; 题解&#xff1a; const int BIG 1 << 30;bool isPowerOfTwo(int n) {return n > 0 && BIG % n 0; }

防火墙--NAT和智能选路的一些知识

目录 NAT 源NAT 包含 目标NAT 包含 双向NAT 防火墙中web页面的nat配置 新建NAT策略 各个选项意思及使用 NAT类型 转换模式 仅转换源地址选项 原始数据包就相当于抓取流量&#xff0c;相当于NAT中acl的配置 转换后的数据包就是转换后的公网地址 配置地址池 端口地…

低代码商城构建专家:Mall-Cook

Mall-Cook&#xff1a;用Mall-Cook&#xff0c;让电商创新触手可及- 精选真开源&#xff0c;释放新价值。 概览 Mall-Cook是一个面向未来的商城低代码开发平台&#xff0c;它通过提供直观的可视化界面&#xff0c;让开发者和商家能够快速构建和部署跨平台的电商解决方案。这个…

国产精品ORM框架-SqlSugar详解 进阶功能 集成整合 脚手架应用 专题二

国产精品ORM框架-SqlSugar详解 SqlSugar初识 专题一-CSDN博客 sqlsugar 官网-CSDN博客 4、进阶功能 5、集成整合 6、脚手架应用 4、进阶功能 4.1、生命周期 Queryable 什么时候操作库 Queryable是一个引用类型 Queryable拷贝机制 4.2、执行Sql 方法列表 方法名 描述 返…

秋招Java后端开发冲刺——MyBatisPlus总结

一、 基本知识 1. 介绍 yBatis-Plus 是一个 MyBatis 的增强工具&#xff0c;在 MyBatis 的基础上增加了大量功能和简化操作&#xff0c;以提高开发效率。 2. 特点 无侵入&#xff1a;只做增强不做改变&#xff0c;引入它不会对现有项目产生影响。依赖少&#xff1a;仅仅依赖 …

智慧校园信息化大平台整体解决方案PPT(75页)

1. 教育信息化政策 教育部印发《教育信息化2.0行动计划》&#xff0c;六部门联合发布《关于推进教育新型基础设施建设构建高质量教育支撑体系的指导意见》&#xff0c;中共中央、国务院印发《中国教育现代化2035》。这些政策文件强调了教育的全面发展、面向人人、终身学习、因…

Linux对文件访问的基本权限

文件权限控制对文件的访问。 有三种权限类别可应用&#xff1a;读取、写入和执行。 权限对文件和目录的影响&#xff1a; 权限 对文件的影响 对目录的影响 r读取 可以读取文件的内容 可以列出目录的内容 w写入 可以更改文件的内容 可以创建或删除目录中任一文件 x执行…

uniapp 支付宝小程序 芝麻免押 免押金

orderStr参数如下&#xff1a; my.tradePay({orderStr:res, // 完整的支付参数拼接成的字符串&#xff0c;从 alipay.fund.auth.order.app.freeze 接口获取success: (res) > {console.log(免押成功);console.log(JSON.stringify(res),不是JOSN);console.log(JSON.stringify…

ClickHouse 查看表的大小

查看表的大小&#xff1a; 使用以下查询语句可以列出表及其占用的磁盘空间大小&#xff1a; SELECTtable,formatReadableSize(sum(bytes_on_disk)) AS size_on_disk FROM system.parts WHERE active GROUP BY table ORDER BY size_on_disk DESC

部署一台本机的ai智能模型

部署ai模型 1.打开地址搜 https://ollama.com/&#xff08;开源ai模型网址&#xff09;下载软件 2.点击安装具有羊驼图标的文件 3.在右下角查看 羊驼图标 4。打开脚本&#xff0c;执行可以切换羊驼安装的位置与环境变量 5.winR 输入cmd,在命令窗口输入ollama命令 如果安装成功…

底软驱动 | Linux字符设备驱动开发基础

文章目录 知识整理--Linux字符设备驱动开发基础字符设备基础1从一个最简单的模块源码说起字符设备驱动工作原理字符设备驱动代码实践--给空模块添加驱动壳子应用程序如何调用驱动 字符设备基础2添加读写接口&#xff08;应用和驱动之间的数据交换&#xff09;驱动中如何操控硬件…

Redis持久化RDB,AOF

目 录 CONFIG动态修改配置 慢查询 持久化 在上一篇主要对redis的了解入门&#xff0c;安装&#xff0c;以及基础配置&#xff0c;多实例的实现&#xff1a;redis的安装看我上一篇&#xff1a; Redis安装部署与使用,多实例 redis是挡在MySQL前面的&#xff0c;运行在内存…

《Linux系统编程篇》vim的使用 ——基础篇

引言 上节课我们讲了&#xff0c;如何将虚拟机的用户目录映射到自己windows的z盘&#xff0c;虽然这样之后我们可以用自己的编译器比如说Visual Studio Code&#xff0c;或者其他方式去操作里面的文件&#xff0c;但是这是可搭建的情况下&#xff0c;在一些特殊情况下&#xf…

Web学习day04

mybatis 目录 mybatis 文章目录 一、查询 1.1结果映射 1.2多条件查询 1.3模糊查询 二、XML 书写规范 三、动态SQL 四、配置文件 4.1settings标签 4.2mappers标签 4.3environments标签 五、案例 5.1数据表 5.2实现类 5.3mapper实现 5.4工具类实现 5.5XML动态…

Ubuntu 安装搜狗输入法

搜狗输入法已支持Ubuntu1604、1804、1910、2004、2010 各系统安装步骤可能略有不同 1、添加中文语言支持 打开 系统设置——区域和语言——管理已安装的语言——在“语言”tab下——点击“添加或删除语言” 弹出“已安装语言”窗口&#xff0c;勾选中文&#xff08;简体&…

【 香橙派 AIpro评测】烧系统到运行并使用Jupyter Lab 界面体验 AI 应用样例(新手福音)

文章目录 ⭐前言⭐初始化开发板⭐下载镜像烧系统⭐开发板初始化系统&#x1f496; 远程ssh&#x1f496;查看ubuntu桌面&#x1f496; 远程向日葵 ⭐体验 AI 应用样例&#x1f496; 运行 jupyterLab&#x1f496; 打开Jupyter Lab页面&#x1f496; 释放内存&#x1f496; 运行…

C#语句与方法

文章目录 语句判断语句循环语句循环控制语句 C#方法&#xff08;函数&#xff09;C#方法定义参数传递 语句 判断语句 语句描述if语句if(判定条件){}&#xff0c;如果条件为真则执行对应代码&#xff0c;反之则跳过if...else语句if(判定条件){}else{}&#xff0c;判定条件为真…

【数据结构】手写堆 HEAP

heap【堆】掌握 手写上浮、下沉、建堆函数 对一组数进行堆排序 直接使用接口函数heapq 什么是堆&#xff1f;&#xff1f;&#xff1f;堆是一个二叉树。也就是有两个叉。下面是一个大根堆&#xff1a; 大根堆的每一个根节点比他的子节点都大 有大根堆就有小根堆&#xff1…