【Netty】时间轮
https://www.modb.pro/db/131799
https://cloud.tencent.com/developer/article/2185938
https://learn.lianglianglee.com/%E4%B8%93%E6%A0%8F/Netty%20%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E5%89%96%E6%9E%90%E4%B8%8E%20RPC%20%E5%AE%9E%E8%B7%B5-%E5%AE%8C/21%20%20%E6%8A%80%E5%B7%A7%E7%AF%87%EF%BC%9A%E5%BB%B6%E8%BF%9F%E4%BB%BB%E5%8A%A1%E5%A4%84%E7%90%86%E7%A5%9E%E5%99%A8%E4%B9%8B%E6%97%B6%E9%97%B4%E8%BD%AE%20HashedWheelTimer.md
时间轮是一种高效利用线程资源进行批量化调度的一种调度模型。把大批量的调度任务全部绑定到同一个调度器上,使用这一个调度器来进行所有任务的管理、触发、以及运行。
时间轮的模型能够高效管理各种任务: 延时任务、周期任务、通知任务。
基本知识
应用场景
应用:在kafka、zookeeper、Netty、Dubbo等高性能组件中都有时间轮使用的方式。
适用于对时效性不高的,可快速执行的,大量这样的“小”任务,能够做到高性能,低消耗。非准实时
应用场景大致有: 心跳检测(客户端探活)、会话、请求是否超时、消息延迟推送、业务场景超时取消(订单、退款单等)
比如Netty动辄管理100w+的连接,每一个连接都会有很多超时任务。比如发送超时、心跳检测间隔等,如果每一个定时任务都启动一个Timer,不仅低效,而且会消耗大量的资源。
在Netty中的一个典型应用场景是判断某个连接是否idle,如果idle(如客户端由于网络原因导致到服务器的心跳无法送达),则服务器会主动断开连接,释放资源。
得益于Netty NIO的优异性能,基于Netty开发的服务器可以维持大量的长连接,单台8核16G的云主机可以同时维持几十万长连接,及时掐掉不活跃的连接就显得尤其重要。
基本模型构成
时间轮是以时间作为刻度, 组成的一个环形队列,这个环形队列采用数组来实现,数组的每个元素称为槽 Bucket,
时间轮是由多个时间格组成,每个时间格代表当前时间轮的基本时间跨度(tickDuration),其中时间轮的时间格的个数是固定的。它以恒定的速率顺时针转动。没转动一步就指向下一个槽,每次转动称之为一个tick。
每个槽位可以放一个定时任务列表,叫HashedWheelBucket;可以是一个双向链表,其中可以设置一个 sentinel 哨兵节点, 作为添加任务和删除任务的起始节点。
槽位链表的每一项表示一个定时任务项(HashedWhellTimeout),其中封装了真正的定时任务TimerTask。
计算槽位: ts = (cs + (ti / si)) % N // 现在指针指向槽cs,我们要添加一个定时时间为ti的定时器,则该定时器将被插入槽ts(timerslot)对应的链表中
时间刻度不够用怎么办?
如果任务不只限定在一天之内呢?比如我有个任务,需要每周一上午九点执行,我还有另一个任务,需要每周三的上午九点执行。
大概的解决办法是:
- 增大时间轮的刻度
- 列表中的任务中添加round属性
- 分层时间轮
方案:增大时间轮的刻度
一天24个小时,一周168个小时,为了解决上面的问题,我可以把时间轮的刻度(槽)从12个增加到168个,
仔细思考一下,会发现这中方式存在几个缺陷:
1. 时间刻度太多会导致时间轮走到的多数刻度没有任务执行,比如一个月就2个任务,我得移动720次,其中718次是无用功。
2. 时间刻度太多会导致存储空间变大,利用率变低,比如一个月就2个任务,我得需要大小是720的数组,如果我的执行时间的粒度精确到秒,那就更恐怖了。
方案:列表中的任务中添加round属性
时间轮每移动到一个刻度时,遍历任务列表,把round值-1,然后取出所有round=0的任务执行。
这样做能解决时间轮刻度范围过大造成的空间浪费,但是却带来了另一个问题:时间轮每次都需要遍历任务列表,耗时增加,当时间轮刻度粒度很小(秒级甚至毫秒级),任务列表又特别长时,这种遍历的办法是不可接受的。
方案:分层时间轮
分层时间轮是这样一种思想:
1. 针对时间复杂度的问题:不做遍历计算round,凡是任务列表中的都应该是应该被执行的,直接全部取出来执行。
2. 针对空间复杂度的问题:分层,每个时间粒度对应一个时间轮,多个时间轮之间进行级联协作。
第一点很好理解,第二点有必要举个例子来说明。
比如我有三个任务:任务一每周二上午九点。任务二每周四上午九点。任务三每个月12号上午九点。三个任务涉及到四个时间单位:小时、天、星期、月份。
拿任务三来说,任务三得到执行的前提是,时间刻度先得来到12号这一天,然后才需要关注其更细一级的时间单位:上午9点。
基于这个思想,我们可以设置三个时间轮:月轮、周轮、天轮。月轮的时间刻度是天。周轮的时间刻度是天。天轮的时间刻度是小时。
初始添加任务时:任务一添加到周轮上,任务二添加到周轮上任务三添加到月轮上。
三个时间轮以各自的时间刻度不停流转。
当周轮移动到刻度2(星期二)时,取出这个刻度下的任务1,丢到天轮上,天轮接管该任务,到9点执行。
当周轮移动到刻度4(周四)时,取出这个刻度下的任务2,丢到天轮上,天轮接管该任务,到9点执行。
当月轮移动到刻度12(12号)时,取出这个刻度下的任务,丢到天轮上,天轮接管该任务,到9点执行。
Netty时间轮定义
1、创建时间轮:
timer.newTimeout(timeout -> {log.info("running task 2..."); }, 5, TimeUnit.SECONDS); // 内部会调用HashedWheelTimer::start() 触发workerThread.start()启动进行时间轮转动
3、停止时间轮
timer.stop(); == 会返回未被执行的timeout
HashedWheelTimer 内部关键实现
private final Queue<HashedWheelTimeout> timeouts = PlatformDependent.newMpscQueue(); -- newMpscQueue最终来自jcTools的高性能队列
private final Queue<HashedWheelTimeout> cancelledTimeouts = PlatformDependent.newMpscQueue();
1、HashedWheelTimeout head;
2、HashedWheelTimeout tail;
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· spring官宣接入deepseek,真的太香了~