Rust 实现Netty HashedWheelTimer时间轮
一、背景
近期在内网上看到一篇文章,文中提到的场景是 系统自动取消 15分钟内 未支付的订单。对于数据量比较少并且实时性要求不那么高的场景,一种简单的方式是轮询数据库,比如每秒轮询一下数据库中所有的数据,处理所有到期的数据。但是如果需要处理的数据量较大,高达百万甚至千万时,这时候如果还轮询数据库就不妥了。
文中给出了这类问题的解决思路,使用延迟队列:Redis ZSort、RabbitMQ 死信队列、Netty 时间轮
二、延迟队列-时间轮

上图几个名词的解释:
- tickMs: 时间轮由多个时间格组成,每个时间格就是 tickMs,它代表当前时间轮的基本时间跨度。
- wheelSize: 代表每一层时间轮的格数
- interval: 当前时间轮的总体时间跨度,interval=tickMs × wheelSize
- startMs: 构造当层时间轮时候的当前时间,第一层的时间轮的 startMs 是 TimeUnit.NANOSECONDS.toMillis(nanoseconds()),上层时间轮的 startMs 为下层时间轮的 currentTime。
- currentTime: 表示时间轮当前所处的时间,currentTime 是 tickMs 的整数倍(通过 currentTime=startMs - (startMs % tickMs 来保正 currentTime 一定是 tickMs 的整数倍),这个运算类比钟表中分钟里 65 秒分钟指针指向的还是 1 分钟)。 currentTime 可以将整个时间轮划分为到期部分和未到期部分,currentTime 当前指向的时间格也属于到期部分,表示刚好到期,需要处理此时间格所对应的 TimerTaskList 的所有任务。
时间轮工作原理:
如上图所示,时间轮是一个存储延迟消息的环形队列,每个元素对应一个延迟任务列表,这个列表是一个双向环形链表,链表中每一项都代表一个需要执行的延迟任务。时间轮会有表盘指针,表示时间轮当前所指时间,随着时间推移,该指针会不断前进,并处理对应位置上的延迟任务列表。
三、Netty 时间轮源码分析
3.1.主要的成员类:
- HashedWheelTimer:调度器,服务启动 Worker 线程,投递新的 延迟任务。
- Worker:工作线程,循环执行,每次sheep(tickMs),根据指针的位置,遍历对应的 延迟任务列表。
- HashedWheelBucket:如上图所示,表盘中的每一个格子,记录着 延迟任务 列表的head、tail。
- HashedWheelTimeout:对应延迟任务列表中的一个元素,当任务过期时,执行TimerTask中的run方法。
- TimerTask:接口类,投递的延迟任务 需要实践接口中的 run 方法
3.2.主要的类方法:
- HashedWheelTimer#newTimeout:投递新的延迟任务 到 timeouts 队列中(Netty 中使用的是 PlatformDependent 队列结构)
- Worker#waitForNextTick: 等待下个ticks,线程sleep(tick)
- Worker#transferTimeoutsToBuckets: 从 timeouts 队列中消费 100000 个 延迟任务,分配到不同的 HashedWheelBucket 的链表中。
- HashedWheelBucket#expireTimeouts:执行到期的任务,然后从队列中剔除已经过期的任务。
四、Rust实现HashedWheelTimer
Rust 实现的 HashedWheelTimer,相比 Netty 只是语言上的区别,算法和数据结构上是一致。源码地址:https://github.com/ZuoFuhong/hashed_wheel_timer
过程中遇到的一些难点:
1.BucketTimeout 双向链表的实现比较费劲,在单线程下使用的 Rc + RefCell 的组合。

2.投递 延迟任务,使用标准库的 mpsc 队列,进行线程间的传递:

3.延迟任务的 run 方法,想过传递闭包,也想过用泛型,这些后续再实现:

4.为什么将WheelTimeout转换成 BucketTimeout:

因为 BucketTimeout 的内部元素 是一个 Rc + RefCell 的组合,不能进行线程传递。如果要支持线程间传递,就要加一个 Arc + Mutex,这样感觉好啰嗦,又得不停的加锁,所以做了一个转换。
5.为什么把 Worker#start 放在 WheelTimer 中执行启动,而观察Netty 是在 WheelTimer#new_timeout 中启动的:

如果不提前启动 Worker#start,使用WheelTimer#clone 后 sender 元素还是 None。
以上,真的不是换个语言那么容易的事,现在回过头发现 java 和 golang 这类有 GC 的语言,真实太爽了,啥都不用管。
五、总结思考
作为一个服务端开发人员,在面试过程中,或多或少会被人问到 “大流量、高并发” 的问题。想要解决这类问题,前提是要能遇到 大规模的用户量,如果没有真实的用户量,可以模拟用户量。当有了 大规模的用户量时,进行验证时,很多问题就会冒出来。这一点在工作中,越发感受深刻:
- 1.系统的承载能力,需要通过 “模拟压测” 的方式去预估用户量。不能等到真正有了用户量的时候,用实践去评估。
- 2.任何一个小问题,在用户量较小的时候,不会出现。一旦用户量上来了,就会成为大问题。
- 3.系统是不断迭代的,要结合背景 因时制宜,早期为了适应市场,快速开发,不要过度的去追求设计上的完美。当下满足诉求即可,后续在迭代中逐步加强。
Rust 实现Netty HashedWheelTimer时间轮的更多相关文章
- 时间轮算法在Netty和Kafka中的应用,为什么不用Timer、延时线程池?
大家好,我是yes. 最近看 Kafka 看到了时间轮算法,记得以前看 Netty 也看到过这玩意,没太过关注.今天就来看看时间轮到底是什么东西. 为什么要用时间轮算法来实现延迟操作? 延时操作 Ja ...
- [从源码学设计]蚂蚁金服SOFARegistry之时间轮的使用
[从源码学设计]蚂蚁金服SOFARegistry之时间轮的使用 目录 [从源码学设计]蚂蚁金服SOFARegistry之时间轮的使用 0x00 摘要 0x01 业务领域 1.1 应用场景 0x02 定 ...
- 时间轮机制在Redisson分布式锁中的实际应用以及时间轮源码分析
本篇文章主要基于Redisson中实现的分布式锁机制继续进行展开,分析Redisson中的时间轮机制. 在前面分析的Redisson的分布式锁实现中,有一个Watch Dog机制来对锁键进行续约,代码 ...
- kafka时间轮的原理(一)
概述 早就想写关于kafka时间轮的随笔了,奈何时间不够,技术感觉理解不到位,现在把我之前学习到的进行整理一下,以便于以后并不会忘却.kafka时间轮是一个时间延时调度的工具,学习它可以掌握更加灵活先 ...
- Redis之时间轮机制(五)
一.什么是时间轮 时间轮这个技术其实出来很久了,在kafka.zookeeper等技术中都有时间轮使用的方式. 时间轮是一种高效利用线程资源进行批量化调度的一种调度模型.把大批量的调度任务全部绑定到同 ...
- SpringBoot定时任务 - 经典定时任务设计:时间轮(Timing Wheel)案例和原理
Timer和ScheduledExecutorService是JDK内置的定时任务方案,而业内还有一个经典的定时任务的设计叫时间轮(Timing Wheel), Netty内部基于时间轮实现了一个Ha ...
- 延时任务-基于netty时间轮算法实现
一.时间轮算法简介 为了大家能够理解下文中的代码,我们先来简单了解一下netty时间轮算法的核心原理 时间轮算法名副其实,时间轮就是一个环形的数据结构,类似于表盘,将时间轮分成多个bucket(比如: ...
- 使用netty HashedWheelTimer构建简单延迟队列
背景 最近项目中有个业务,需要对用户新增任务到期后进行业务处理.使用定时任务定时扫描过期时间,浪费资源,且不实时.只能使用延时队列处理. DelayQueue 第一想到的是java自带的延时队列del ...
- 时间轮算法(TimingWheel)是如何实现的?
前言 我在2. SOFAJRaft源码分析-JRaft的定时任务调度器是怎么做的?这篇文章里已经讲解过时间轮算法在JRaft中是怎么应用的,但是我感觉我并没有讲解清楚这个东西,导致看了这篇文章依然和没 ...
随机推荐
- CSAcademy Prefix Suffix Counting 题解
CSAcademy Prefix Suffix Counting 题解 目录 CSAcademy Prefix Suffix Counting 题解 题意 思路 做法 程序 题意 给你两个数字\(N\ ...
- 分布式系统一致性算法(Paxos)
CAP理论 一致性(Consistency) 可用性(Availability) 分区容错性(网络分区)Partition toleranceCAP理论的特点,就是CAP只能满足其中 ...
- [C# Expression] 之动态创建表达式
上一篇中说到了 Expression 的一些概念性东西,其实也是为了这一篇做知识准备.为了实现 EFCore 的多条件.连表查询,简化查询代码编写,也就有了这篇文章. 在一些管理后台中,对数据进行 ...
- c++模板移除引用
背景 一个函数内部需将函数内的一个变量转为其参数的类型, 函数的参数是引用 本文要演示的环境需要c++11支持(使用了新的关键字 using) 例如 get_value 内部将dobuble类型数据转 ...
- c++11之 algorithm 算法库新增 minmax_element同时计算最大值和最小值
0.时刻提醒自己 Note: vector的释放 1. minmax_element 功能 寻找范围 [first, last) 中最小和最大的元素. 2. 头文件 #include <algo ...
- 【LeetCode】912. Sort an Array 解题报告(C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 库函数排序 桶排序 红黑树排序 归并排序 快速排序 ...
- 【LeetCode】1014. Capacity To Ship Packages Within D Days 解题报告(Python)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 日期 题目地址:https://leetcode.c ...
- 【LeetCode】75. Sort Colors 解题报告(Python)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 计数排序 双指针 日期 题目地址:https://l ...
- A. Watchmen(Codeforces 650A)
A. Watchmen time limit per test 3 seconds memory limit per test 256 megabytes input standard input o ...
- PAT甲组 1010 Radix (二分)
1010 Radix (25分) Given a pair of positive integers, for example, \(6\) and \(110\), can this equatio ...