基于 golang 从零到一实现时间轮算法 (一)

news2025/1/22 21:04:55

前言

时间轮是用来解决海量百万级定时器(或延时)任务的最佳方案,linux 的内核定时器就是采用该数据结构实现。


应用场景

  1. 自动删除缓存中过期的 Key:缓存中设置了 TTL 的 kv,通过把该 key 对应的 TTL 以及回调方法注册到 timewheel,到期直接删除
  2. 延时任务,将任务注册到 timewheel,过期自动触发执行
  3. 在 TcpServer 中,用来管理海量 Tcp 连接的超时定时器,如 zinx 的定时器 实现

简单时间轮

在这里插入图片描述
如上图,一个普通的时间轮,类似于时钟表盘,指针(pointer)每隔一段时间前进一格(interval,tick 一次),一圈代表一个周期(circle),定时任务以链表(双向)方式置放在表盘的刻度处,当指针前进到当前位置时,遍历任务链表,执行相应的任务。

基本模型构成

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

从开发角度而言,实现一个时间轮:

  1. 时间轮是一个由固定长度 length 的数组(本例子中就是 [1,12])构造而成的环形队列
  2. 时间轮的长度决定了延时任务的刻度,假设上面的刻度为 1s(即时间轮 1s 前进一格),那么该时间轮只能表达延时任务在 1s 至 12s内的任务;时间轮的长度也即时间轮的周期(12s)
  3. 注册任务按照 当前刻度 + 延时时长 % 时间轮周期 计算得出,假设当前指针在 5s的位置,此时添加一个延时周期为 5s 的任务,那么该任务需要注册到刻度为 10s 的格子对应的任务链表中
  4. 数组中的每个元素都指向一个双向链表,用于存储对应的延时任务
  5. 时间轮的插入复杂度是 O(1),删除指定节点的复杂度是O(n),因为需要遍历双向链表以查找到要删除的节点
  6. 当时间轮指针转动到对应的单元格时,顺序执行双向链表中存储的任务基础时间轮的缺点是无法注册延时超过时间轮周期的任务,如何解决呢?

基础时间轮的缺点是无法注册延时超过时间轮周期的任务,如何解决呢?

解决方法 1:加 circle 计数器

此方法相当于给双向链表中存储的任务加多一个 “圈数” 的维度
在这里插入图片描述
• 假设时间轮有8个刻度
• 计时器值 = 17
• 走两圈时间轮+多走1个刻度
• 在"0"这个桶中设置计时器
• 与计时器一同保留#圈数
• 在到期处理时,如果#圈数 > 0,则重新插入计时器

注意,此策略中,时间轮指针每前进一格,需要把此格对应的任务链表中,所有的任务的 circle 计数器都减 1,如果 circle==0,那么说明,任务时间已到期,执行该任务

解决方法 2:层级时间轮

这是一种典型的 “空间换时间” 的思路,按照时间轮周期的倍数进行合理分层,有两个优点:

  1. 避免任务堆积在某个 slot 上
  2. 支持任意长时间的延时任务注册
    在这里插入图片描述
    复用之前的案例,第一层的时间轮 tickMs=1ms, wheelSize=20, interval=20ms。第二层的时间轮的 tickMs 为第一层时间轮的 interval,即为 20ms。每一层时间轮的 wheelSize 是固定的,都是 20,那么第二层的时间轮的总体时间跨度 interval 为 400ms。以此类推,这个 400ms 也是第三层的 tickMs 的大小,第三层的时间轮的总体时间跨度为 8000ms。

关于层级时间轮,可以参考普通的时间。多层时间轮的概念也非常清晰,将时间轮分为多个,每两个轮之间是进位的关系,例如最普遍的秒,分,时.即:

  • 秒级时间轮上,设有60个槽, 每两个槽之间的时间为1s.
  • 分钟级时间轮上,设有60s个槽,每两个槽之间的时间为1min
  • 小时级时间轮上,设有24个槽,每两个槽之间的时间为1h
    在这里插入图片描述
    这样,秒级每走60个槽,时间过去一分钟,秒级时间轮归零,分级时间轮走一个槽; 分级时间轮每走过60个槽,时间过去一小时,分级时间轮归零,小时级时间轮走一个槽.

通过三级时间轮,只需要遍历60+60+60 =180个槽,就可以成为一个精度为1s, 周期为60x60x60 = 216000s的定时调度器.


参考

https://zhuanlan.zhihu.com/p/658079556,
https://pandaychen.github.io/2022/05/28/A-TIMEWHEEL-ANALYSIS/
https://juejin.cn/post/7083795682313633822

属于在以上博客基础上记录的个人笔记。

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

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

相关文章

云安全-云原生k8s攻击点(8080,6443,10250未授权攻击点)

0x00 k8s简介 k8s(Kubernetes) 是容器管理平台,用来管理容器化的应用,提供快速的容器调度、弹性伸缩等诸多功能,可以理解为容器云,不涉及到业务层面的开发。只要你的应用可以实现容器化,就可以部…

css——半圆实心

案例 代码 <view class"circleBox"></view>.circleBox {width: 50px;height: 100px;background: red;border-radius: 100px 0 0 100px; }

史上最详细注释,用flask写一个博客系统

文本用flask写个博客系统&#xff0c;源码带有详细注释&#xff0c;通俗易懂&#xff0c;拿去就能用。博客效果如下&#xff0c;博客首页&#xff1a; 这个博客麻雀虽小&#xff0c;但五脏俱全。有如下功能&#xff1a; 博客文章浏览用户注册用户登录/登出发文章/修改文章/删除…

Linux设置ssh免密登录

ssh连接其他服务器 基本语法 ssh 另一台机器的ip地址 连接后输入连接主机用户的密码&#xff0c;即可成功连接。 输入exit 可以登出&#xff1b; 由于我配置了主机映射所以可以不写ip直接写映射的主机名即可&#xff0c;Linux配置主机映射的操作为 vim /etc/hosts # 我自己…

Linux上编译sqlite3库出现undefined reference to `sqlite3_column_table_name‘

作者&#xff1a;朱金灿 来源&#xff1a;clever101的专栏 为什么大多数人学不会人工智能编程&#xff1f;>>> 在Ubuntu 18上编译sqlite3库后在运行程序时出现undefined reference to sqlite3_column_table_name’的错误。网上的说法是说缺少SQLITE_ENABLE_COLUMN_M…

【使用Python编写游戏辅助工具】第四篇:Windows窗口操作

前言 这里是【使用Python编写游戏辅助工具】的第四篇&#xff1a;Windows窗口操作。本文主要介绍使用Python来实现Windows窗口的各种操作。 Windows窗口操作是游戏辅助功能中不可或缺的一部分。 Windows窗口操作指的是与Windows操作系统中的窗口进行交互和控制的操作&#xff…

Docker安装ElasticSearch7.8.0

Docker安装ElasticSearch7.8.0 1&#xff1a;docker可能会拉取不了es&#xff0c;此时可以配置一个很好用的镜像源&#xff08;daocloud&#xff09;&#xff0c;下载非常快&#xff1a; curl -sSL https://get.daocloud.io/daotools/set_mirror.sh | sh -s http://f1361db2.…

iTerm2 自动连接服务器配置

iTerm2 自动连接服务器配置 创建ssh_conf.sh配置文件 touch ssh_conf.sh编辑配置文件内容 #!/usr/bin/expect -f set user root set host 119.xxx.xxx.xxx set port 22 set password xxxx set timeout 30 spawn ssh -p $port $user$host expect "$user$hosts password:…

测试用例设计方法 —— 场景法详解

场景法是通过运用场景来对系统的功能点或业务流程的描述&#xff0c;从而提高测试效果的一种方法。 场景法一般包含基本流和备用流&#xff0c;从一个流程开始&#xff0c;通过描述经过的路径来确定的过程&#xff0c;经过遍历所有的基本流和备用流来完成整个场景。 场景主要…

玻色量子“天工量子大脑”亮相中关村论坛,大放异彩

2023年5月25日至30日&#xff0c;2023中关村论坛&#xff08;科博会&#xff09;在北京盛大召开。中关村论坛&#xff08;科博会&#xff09;是面向全球科技创新交流合作的国家级平台行业盛会&#xff0c;由科技部、国家发展改革委、工业和信息化部、国务院国资委、中国科学院、…

【Linux学习笔记】进程概念(上)

1. 冯诺依曼体系结构2. 操作系统的作用3. 进程 1. 冯诺依曼体系结构 如图&#xff0c;这是一个冯诺依曼体系结构简图 其中这里的存储器指的是内存&#xff01; 用通俗的话来解释这个图&#xff0c;就是数据从输入设备进入&#xff0c;然后进入到存储器&#xff0c;CPU从存储器…

【PWN · heap | UAF】[BJDCTF 2020]YDSneedGirlfriend

一篇裸的、便于学习UAF的题目和笔记 前言 UAF-释放后重用&#xff0c;这一题和wiki上教学的那一题一样&#xff0c;是纯的裸UAF题目 一、题目 二、分析 题目中del函数&#xff0c;在释放申请的堆块后&#xff0c;并没有将置零&#xff0c;存在UAF漏洞。 分析add函数&#xf…

Tailwind CSS vs 现代CSS,Tailwind CSS 会像CSS-in-JS 一样亡?

本文是 关于Tailwind CSS 与 现代 CSS之间比较的文章。文章中作者详细比较了这两种CSS开发方法的优缺点。他指出&#xff0c;Tailwind CSS是一种基于类的CSS框架&#xff0c;提供了快速开发网站的便利性&#xff0c;但可能导致HTML代码的臃肿。另一方面&#xff0c;现代CSS方法…

[双指针] (四) LeetCode 18.四数之和

[双指针] (四) LeetCode 18.四数之和 文章目录 [双指针] (四) LeetCode 18.四数之和题目解析解题思路代码实现总结 18. 四数之和 题目解析 (1) 从一个数组中找一个目标值target (2) target nums[a] nums[b] nums[c] nums[d] 解题思路 和上一道题三数之和一样, 我们把四…

刷到这篇文章的老师,就是老天在帮你

作为一名奋斗在教育战线的老师&#xff0c;是否曾在成绩查询这个环节中倍感头大&#xff1f; 统计是个繁琐又重要的工作&#xff0c;但有了正确的工具&#xff0c;一切都变得无所谓&#xff01; 什么是成绩查询&#xff1f; 成绩查询&#xff0c;顾名思义&#xff0c;就是学生…

uni-app 应对微信小程序最新隐私协议接口要求的处理方法

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 一&#xff0c;问题起因 最新在开发小程序的时候&#xff0c;调用微信小程序来获取用户信息的时候经常报错一个问题 fail api scope is not declared in the privacy agreement&#xff0c;api更具公告…

Linux | 文件系统

目录 前言 一、预备知识 二、文件相关的系统调用 1、C语言的文件操作 2、系统调用接口 &#xff08;1&#xff09;open函数 &#xff08;2&#xff09;close函数 &#xff08;3&#xff09;write函数 &#xff08;4&#xff09;read函数 3、代码实操 三、深入理解文…

k8s:endpoint

在 Kubernetes 中&#xff0c;Endpoint 是一种 API 对象&#xff0c;它用于表示集群内某个 Service 的具体网络地址。换句话说&#xff0c;它连接到一组由 Service 选择的 Pod&#xff0c;从而使它们能够提供服务。每个 Endpoint 对象都与相应的 Service 对象具有相同的名称&am…

python判断图片主颜色

一 、 问题&#xff1a;python判断图片主颜色 python判断图片主颜色&#xff08;HSV&#xff09; 二 、 项目背景&#xff1a; app选项是否能被点击&#xff0c;判断执行逻辑。做自动化测试的朋友肯定遇到好多次&#xff0c;按钮属性无法判别时&#xff0c;就需要自己将app选…

shell script中的数值运算declare和$((运算式 ))

linux中变量定义默认是字符串类型&#xff0c;如要进行数值运算&#xff0c;需要先声明变量类型&#xff0c;或者通过固定格式来计算 看案例 如果不通过固定格式&#xff0c;直接 echo 55 如图&#xff0c;结果显示的55本身 可以写成 declare -i var#声明变量integrate类型&…