时间轮的golang实践浅析
引言
- 下列代码模仿一段RPC请求的执行过程,执行后会有哪些问题:
RPC代码示例 - 答案:因为超时控制后未阻断后续请求,导致并发读写产生Panic
- 思考:客户端发起 HTTP 请求后,如果在指定时间内没有收到服务器的响应,则自动断开连接,超时控制是如何工作的?
什么是时间轮
- 思考:一定有一个类似于定时器的工具在执行,到时间后,中断任务。那么这个定时器是什么样的数据结构?又是如何实现这个定时功能的?
- 理论上
- 客户端发起请求后,立即创建(启动)一个 Timer:到期间隔为 d,到期后执行 “断开连接” 的操作。
- 如果到期间隔 d 以内收到了服务器的响应,客户端就删除(停止)这个 Timer。
- 如果一直没有收到响应,则 Timer 最终会到期,然后执行 “断开连接” 的操作。
- 实际上
- 现代的 Web 服务动辄管理 100w+ 的连接,每个连接都会有很多超时任务(比如发送超时、心跳检测等),如果每个超时任务都对应一个 Timer,性能会比较低下
- 破解之法:采用时间轮实现的 Timer来管理连接任务,使得创建和删除连接任务的时间复杂度为 O(1)
时间轮种类和设计思路
- 常见的时间轮实现有两种:
- 简单时间轮(Simple Timing Wheel)—— 比如 Netty4 的 HashedWheelTimer
- 层级时间轮(Hierarchical Timing Wheels)—— 比如 Kafka 的 Purgatory
简单时间轮
简单时间轮的设计思路
- 一个 简单时间轮就是一个循环列表,列表中的每一格包含一个定时任务列表(双向链表)。一个时间单位为 u、大小为 n 的简单时间轮,可以包含的定时任务的最大到期间隔为 n*u。
- 以 u 为 1ms、n 为 3 的简单时间轮为例,可以包含的定时任务的最大到期间隔为 3ms
简单时间轮示例

简单时间轮的实现
index := 0timingWheels := make([]interface{}, 10)for { // 循环消费任务time.Sleep(1 * time.Second)index = index % len(timingWheels)task := timingWheels[index]fmt.Println(task) // exec taskindex++}{ // 增加任务x := 2 // 2s 后执行task_i := "任务i"timingWheels[(index+x)%len(timingWheels)] = task_i}
简单时间轮的缺陷
- 一旦选定 n,就不能包含到期间隔超过 n*u 的定时任务
- 解决办法:选择较大的n
- 引申问题:如果定时任务的到期时间跨度较大,就会选择较大的 n,在定时任务较少时会造成很大的空间浪费
- 引申问题的解决办法:在定时任务中增加记录 round 轮次信息,可以有效弥补上述两个缺点。同样以上面 u 为 1ms、n 为 3 的简单时间轮为例,初始时间指向第 1 格;此时如果要创建到期时间为 4ms 的定时任务,可以在该任务中设置 round 为 1(4/3 取整),剩余到期时间用 4ms 减去 round*3ms 等于 1ms,因此放到第 2 格;等到当前时间指向第 2 格时,判断任务中的 round 大于 0,所以不会删除并执行该任务,而是对其 round 减一(于是 round 变为 0);等到再过 3ms 后,当前时间再次指向第 2 格,判断任务中的 round 为 0,进而删除并执行该任务
index := 0timingWheels := make([]timingWheelsTask, 10)for { // 循环消费任务time.Sleep(1 * time.Second)index = index % len(timingWheels)cur := timingWheels[index]if cur.round != 0 {cur.round--} else {fmt.Println(cur.task) // exec task}index++}{ // 增加任务x := 20 // 2s 后执行round_i := x / len(timingWheels)index_i := x % len(timingWheels)task_i := timingWheelsTask{round: round_i,task: "任务i",}timingWheels[index_i] = task_i}type timingWheelsTask struct {round inttask interface{}}
- 再次引申问题:每格轮子只能存放一个task,如果在同一时间,需要执行多个任务,怎么办?
- 再次引申问题的解决办法:将timingWheelsTask结构体修改为:
type timingWheelsTask struct {taskList []TaskList}type TaskList struct {round inttask interface{}}
- 再一次引申问题:TaskList的处理时间是O(n),如果定时任务数量很大,分摊到每一格的定时任务列表就会很长,这样的处理性能显然是让人无法接受的,特别是对于时间精度要求比较高的任务,另外就是由于list过长,导致for循环完list后,当前index的时间已经过了,长此以往,会导致整体时间轮的精度不准确,延误后面的task执行。
- 问题到此看似无解
层级时间轮
层级时间轮的设计思路
- 层级时间轮 通过使用多个时间轮,并且对每个时间轮采用不同的 u,可以有效地解决简单时间轮及其变体实现的问题
- 理论上
- 每一层时间轮的大小都固定为 n,第一层时间轮的时间单位为 u,那么第二层时间轮(我们称之为第一层时间轮的溢出时间轮 Overflow Wheel)的时间单位就为 n*u,以此类推。
- 除了第一层时间轮是固定创建的,其他层的时间轮(均为溢出时间轮)都是按需创建的。
- 原先插入到高层时间轮(溢出时间轮)的定时任务,随着时间的流逝,会被降级重新插入到低层时间轮中
层级时间轮实例
- 以 u 为 1ms、n 为 3 的层级时间轮为例,第一层时间轮的时间单位为 1ms、大小为 3,第二层时间轮的时间单位为 3ms、大小为 3,以此类推

- 运行原理
- 初始时,只有第一层(Level 1)时间轮,假设当前时间(蓝色箭头)指向第 1 格(此时:到期间隔为 [0ms, 1ms) 的定时任务放第 1 格,[1ms, 2ms) 的放第 2 格,[2ms, 3ms) 的放第 3 格)。
- 此时我们创建一个到期间隔为 2ms 的定时任务 task1,按规则该任务会被插入到第一层时间轮的第 3 格。
- 同一时刻,我们再次创建一个到期间隔为 4ms 的定时任务 task2,因为到期间隔超过了第一层时间轮的间隔范围,所以会创建第二层(Level 2)时间轮;第二层时间轮中的当前时间(蓝色箭头)也指向第 1 格,按规则该任务会被插入到第二层时间轮的第 2 格。
- 随着时间的流逝,过了 2ms 后,第一层时间轮中的当前时间指向第 3 格,这一格包含的任务 task1 会被删除并执行;此时,第二层时间轮的当前时间没有变化,依然指向第 1 格。
- 随着时间的流逝,又过了 1ms 后,第一层时间轮中的当期时间指向第 1 格,这一格中没有任务;此时,第二层当前时间指向第 2 格,这一格包含的任务 task2 会被删除并重新插入时间轮,因为剩余到期时间为 1ms,所以 task2 会被插入到第一层时间轮的第 2 格。
- 随着时间的流逝,又过了 1ms 后,第一层时间轮中的当前时间指向第 2 格,这一格包含的定时任务 task2 会被删除并执行;此时,第二层时间轮的当前时间没有变化,依然指向第 2 格。
层级时间轮的实现
- timingwheel源码
- Kafka 的变体实现【指针不动,桶往前走】
- 使用大小为 wheelSize 的数组来表示一层时间轮,其中每一格是一个 bucket,每个 bucket 的时间单位为 tick。
- 这个时间轮数组并没有模拟循环列表的行为(如图左所示),而是模拟了哈希表的行为。具体而言(如图右所示),这个时间轮数组会随着 currentTime 的流逝而移动,也就是说 currentTime 永远是指向第一个 bucket 的,每个落到该时间轮的定时任务,都会根据哈希函数 (expiration/tick)%wheelSize 散列到对应的 bucket 中。
- Kafka Timer 实现源码
- 时钟驱动方式
- 常规的时间轮实现中,会在一个线程中每隔一个时间单位 tick 就醒来一次,并驱动时钟走向下一格,然后检查这一格中是否包含定时任务。如果时间单位 tick 很小(比如 Kafka 中 tick 为 1ms)并且(在最低层时间轮的)定时任务很少,那么这种驱动方式将会非常低效
- Kafka 的层级时间轮实现中,利用了 Java 内置的 DelayQueue 结构,将每一层时间轮中所有 “包含有定时任务的 bucket” 都加入到同一个 DelayQueue 中,然后 等到有 bucket 到期后再驱动时钟往前走,并逐个处理该 bucket 中的定时任务。
- 图解

- 往层级时间轮中添加一个定时任务 task1 后,会将该任务所属的 bucket2 的到期时间设置为 task1 的到期时间 expiration(= 当前时间 currentTime + 定时任务到期间隔 duration),并将这个 bucket2 添加(Offer)到 DelayQueue 中。
- DelayQueue(内部有一个线程)会等待 “到期时间最早(earliest)的 bucket” 到期,图中等到的是排在队首的 bucket2,于是经由 poll 返回并删除这个 bucket2;随后,时间轮会将当前时间 currentTime 往前移动到 bucket2 的 expiration 所指向的时间(图中是 1ms 所在的位置);最后,bucket2 中包含的 task1 会被删除并执行。
- 上述 Kafka 层级时间轮的驱动方式是非常高效的。虽然 DelayQueue 中 offer(添加)和 poll(获取并删除)操作的时间复杂度为 O(log n),但是相比定时任务的个数而言,bucket 的个数其实是非常小的(也就是 O(log n) 中的 n 很小),因此性能也是没有问题的
时间轮源码分析
- PriorityQueue 优先队列
- Push
- Pop
- PeekAndShift
- DelayQueue 延时队列
- Offer
- Poll
- Timer 定时器 event
- getBucket
- setBucket
- bucket 时间轮的桶
- Expiration
- SetExpiration
- Add
- Remove
- Flush
- TimingWheel 时间轮本轮
- add
- addOrRun
- advanceClock
- Start
- AfterFunc
- Scheduler 调度时间轮
- ScheduleFunc
参考
- 层级时间轮的 Golang 实现
相关文章:
时间轮的golang实践浅析
引言 下列代码模仿一段RPC请求的执行过程,执行后会有哪些问题: RPC代码示例答案:因为超时控制后未阻断后续请求,导致并发读写产生Panic思考:客户端发起 HTTP 请求后,如果在指定时间内没有收到服务器的响应…...
Linux命令_stress 快速模拟CPU、内存、磁盘消耗
ping的安装命令:apt-get install -y inetutils-ping 会遇到Unable to locate package inetutils-ping问题 正确的操作是: ** 这时候需要敲:apt-get update,这个命令的作用是:同步 /etc/apt/sources.list 和 /etc/apt/…...
可视化绘图技巧100篇分析篇(二)-生存曲线(LM曲线)
目录 前言 几个高频面试题目 roc曲线和生存曲线区别 生存曲线模型 生存曲线组件讲解...
UP主发车啦!撩人仙侠文系列,谁来管管这个反派啊!
本人书龄4年,平时很爱看小说,阅遍无数经典修仙文,熬夜党的最爱啊!!!!我心中的仙侠top,都是我的心头爱。 一般我都会跟朋友说这六本五星级仙侠好文,如果她们不看…...
K8S使用持久化卷存储到NFS(NAS盘)
参考文章:K8S-v1.20中使用PVC持久卷 - 知乎 目录 1、概念: 1.1 基础概念 1.2 PV的配置 1.2.1 静态PV配置 1.2.2 动态PV配置 1.2.3 PVC与PV的绑定 1.2.4 PVC及PV的使用 2 部署PV及PVC 2.1 所有K8S机器都需要安装NFS程序 2.2 仅针对需要暴露文件…...
一图看懂 multidict 模块:类似于字典的键值对集合,键可以多次出现,资料整理+笔记(大全)
本文由 大侠(AhcaoZhu)原创,转载请声明。 链接: https://blog.csdn.net/Ahcao2008 一图看懂 multidict 模块:类似于字典的键值对集合,键可以多次出现,资料整理笔记(大全) 🧊摘要🧊模…...
django CBV 与 DRF APIView源码分析
django CBV源码分析 在django框架中,视图层中的逻辑即可以使用函数处理也可以使用类进行处理,如果在视图层中使用函数处理请求,就是FBV(function base views),如果在视图层中使用类处理请求,就是CBV(class base views…...
沃尔玛入驻教程:中国卖家如何免费、快速入驻沃尔玛walmart.com?
作为一家全球知名的零售巨头,沃尔玛(Walmart)的在线商城walmart.com拥有庞大的消费者基础和巨大的商机。对于中国的卖家来说,入驻沃尔玛的平台是一个很好的机会,但是有没有什么方法可以免费、快速入驻呢?有…...
《花雕学AI》Poe 上的四种 AI 机器人,你该怎么选?ChatGPT、Sage、Claude 和 Dragonfly对比
虽然 ChatGPT 是一项革命性的技术,但它作为一个消费产品却有点失败。你可能会花很长时间等待 OpenAI 的聊天机器人加载,或者根本无法使用它,因为它太大了。就算你能用上它,它也很缓慢,而且它的界面也很丑陋。它甚至没有…...
localStorage
目录 localStorage与sessionStorage localStorage的Set与Get localStorage传递参数 localStorage与sessionStorage 现代浏览器提供了一种被称为"Web Storage APIs"(Web 存储接口)的机制,允许在同一浏览器的不同标签页之间共享数…...
二十五、SQL 数据分析实战(9个中等难度的SQL题目)
文章目录 题目1: App 使用频率分析题目2: App 下载情况统计题目3: 寻找活跃学习者题目4: 商品分类整理题目5: 商品销售分析题目6: 网约车司机收益统计题目7: 网站登录时间间隔统计题目8: 不同区域商品收入统计题目9: 信贷逾期情况统计 题目1: App 使用频率分析 现有一张用户使…...
JavaSE_02基本语法-编程单词词汇
boolean [bʊlɪən] 变量的基本数据类型之一:布尔型const [kɒnst] n. 常量,常数constant [kɒnst(ə)nt] n. [数] 常数;恒量continue [kən’tɪnjuː] vi. 继续,连续;default [dɪ’fɔːlt; diːfɔːlt] 默认的,缺…...
区间预测 | MATLAB实现QRDNN深度神经网络分位数回归时间序列区间预测
区间预测 | MATLAB实现QRDNN深度神经网络分位数回归时间序列区间预测 目录 区间预测 | MATLAB实现QRDNN深度神经网络分位数回归时间序列区间预测效果一览基本介绍模型描述程序设计参考资料 效果一览 基本介绍 MATLAB实现QRDNN深度神经网络分位数回归时间序列区间预测。QRDNN模型…...
如何使用aframe.js构建一个简单的VR播放器
在当今这个信息化的时代,虚拟现实(VR)已经开始逐渐成为一种新的生活方式。作为一名前端开发工程师,在学习和探索VR技术方面,aframe.js是一个非常有趣和有用的工具。在本文中,我将介绍如何使用aframe.js构建…...
Fiddler抓包工具常见功能介绍,还不会的进来看
目录 Fiddler的功能面板 一、Statistics数据统计面板,性能分析 二、Inspectors查看请求与响应 三、Filters过滤器 1、User Filters启用 2、Action 3、过滤器实际应用 四、AutoResponder请求重定向 1、什么是请求重定向? 2、为什么要用这个功能&…...
基于海鸥算法优化的核极限学习机(KELM)分类算法-附代码
基于海鸥算法优化的核极限学习机(KELM)分类算法 文章目录 基于海鸥算法优化的核极限学习机(KELM)分类算法1.KELM理论基础2.分类问题3.基于海鸥算法优化的KELM4.测试结果5.Matlab代码 摘要:本文利用海鸥算法对核极限学习机(KELM)进行优化,并用于分类 1.KE…...
JAVA代码规范审查
JAVA代码规范审查 1. 添加必要的注释 所有的类都必须添加创建者和创建日期,以及简单的注释描述 方法内部的复杂业务逻辑或者算法,需要添加清楚的注释 一般情况下,注释描述类、方法、变量的作用 任何需要提醒的警告或TODO,也要注…...
Centos8安装redis7
1.下载: 官网下载:Download | Redis 把安装包上传至服务器: 2.安装: 解压redis: [rootnode202 ~]# cd /usr/local/soft/ [rootnode202 soft]# tar -zxvf redis-7.0.11.tar.gz 安装: 进入redis-7.0.1…...
RabbitMQ详解(一):Linux安装
消息队列概念 消息队列是在消息的传输过程中保存消息的容器。队列的主要目的是提供路由并保证消息的传递;如果发送消息时接收者不可用,消息队列会保留消息,直到可以成功地传递它。 常见的消息队列 RabbitMQ 基于AMQP(高级消息队列协议)基础上…...
Mojo:比 Python 快 35000 倍的 AI 编程语言
Mojo:比 Python 快 35000 倍的 AI 编程语言 Mojo是一门刚刚发布的面向 AI 开发人员的编程语言。 Mojo 被设计为 Python 的超集,所以如果你已经掌握了 Python,学习 Mojo 会很容易。关键是 Mojo 将 Python 的易用性与 C 语言的性能相结合&am…...
中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试
作者:Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位:中南大学地球科学与信息物理学院论文标题:BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接:https://arxiv.…...
8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂
蛋白质结合剂(如抗体、抑制肽)在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上,高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术,但这类方法普遍面临资源消耗巨大、研发周期冗长…...
UDP(Echoserver)
网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法:netstat [选项] 功能:查看网络状态 常用选项: n 拒绝显示别名&#…...
将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?
Otsu 是一种自动阈值化方法,用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理,能够自动确定一个阈值,将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...
CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...
Linux-07 ubuntu 的 chrome 启动不了
文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了,报错如下四、启动不了,解决如下 总结 问题原因 在应用中可以看到chrome,但是打不开(说明:原来的ubuntu系统出问题了,这个是备用的硬盘&a…...
零基础设计模式——行为型模式 - 责任链模式
第四部分:行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习!行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想:使多个对象都有机会处…...
Java线上CPU飙高问题排查全指南
一、引言 在Java应用的线上运行环境中,CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时,通常会导致应用响应缓慢,甚至服务不可用,严重影响用户体验和业务运行。因此,掌握一套科学有效的CPU飙高问题排查方法&…...
LeetCode - 199. 二叉树的右视图
题目 199. 二叉树的右视图 - 力扣(LeetCode) 思路 右视图是指从树的右侧看,对于每一层,只能看到该层最右边的节点。实现思路是: 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...
Redis:现代应用开发的高效内存数据存储利器
一、Redis的起源与发展 Redis最初由意大利程序员Salvatore Sanfilippo在2009年开发,其初衷是为了满足他自己的一个项目需求,即需要一个高性能的键值存储系统来解决传统数据库在高并发场景下的性能瓶颈。随着项目的开源,Redis凭借其简单易用、…...
