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中是怎么应用的,但是我感觉我并没有讲解清楚这个东西,导致看了这篇文章依然和没 ...
随机推荐
- CF760A Petr and a calendar 题解
Content 输入两个数 \(m,d\),请输出 \(2017\) 年 \(m\) 月的日历[其中第一天是星期 \(d\)(如果 \(d=7\) 就是星期天)]需要印的列数. 格式见题目所述. 数据 ...
- CF1433A Boring Apartments 题解
Content 我们把仅由一个或多个相同的数位组成的数字叫作"无聊的数字".我们把 \(\leqslant 10000\) 的这种数字按照以下规则排列: 首先,将仅由 \(1\) ...
- LuoguP7041 [NWRRC2016]King's Heir 题解
Content 给出现在的日期,请从 \(n\) 个人当中选出一个人,使得他是所有成年人(\(\geqslant 18\) 岁的人)中年龄最小的. 数据范围:设日期为 \(yy/mm/dd\),则有 ...
- 深入理解Akka Actor模型
Carl Hewitt 在1973年对Actor模型进行了如下定义:"Actor模型是一个把'Actor'作为并发计算的通用原语". Actor是异步驱动,可以并行和分布式部署及运 ...
- 【LeetCode】284. Peeking Iterator 解题报告(Python)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 题目地址:https://leetcode.com/problems/peeking-i ...
- 【LeetCode】341. Flatten Nested List Iterator 解题报告(Python&C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 递归+队列 栈 日期 题目地址:https://lee ...
- Sentry 开发者贡献指南 - SDK 开发(事件负载)
内容整理自官方开发文档 系列 Docker Compose 部署与故障排除详解 1 分钟快速使用 Docker 上手最新版 Sentry-CLI - 创建版本 快速使用 Docker 上手 Sentr ...
- Laravel 使用 maatwebsite/Excel 3.1 实现导入导出的简单方法
官方文档 https://docs.laravel-excel.com/3.1/getting-started git地址 https://github.com/maatwebsite/Laravel ...
- Nginx 简单配置反向代理
nginx 配置反向代理,转发请求到另一个域名 在server中加入 location /${alias} { proxy_buffer_size 128k; proxy_buffers 32 32k ...
- [平台建设] Spark任务的诊断调优
背景 平台目前大多数任务都是Spark任务,用户在提交Spark作业的时候都要进行的一步动作就是配置spark executor 个数.每个executor 的core 个数以及 executor 的 ...