《深潜Dubbo》· HashedWheelTimer定时轮算法

如题所述

第1个回答  2024-08-13
HashedWheelTimer定时轮算法被广泛应用,包括netty、dubbo乃至操作系统Linux,主要用于管理及维护大量Timer调度算法。

HashedWheelTimer呈环形结构,类似时钟,分为众多槽位,每个槽位代表一个时间间隔,存储定时任务的双向链表。指针周期性跳动,到达某个槽位即执行该槽位定时任务。

定时轮实现中,根据职责不同,分为时钟引擎、时钟槽、定时任务三个主要角色,深入理解其实现,本文将略去具体实现语言的特性。

定时任务——HashedWheelTimeout在具体实现中扮演双重角色,既是双向链表的节点,也是实际调度任务TimerTask的容器。引擎在滴答运行起始时刻使用&取hash装入对应的时钟槽。

关键属性包括:next和prev(当前定时任务在链表中的前驱和后继引用)、task(实际被调度的任务)、deadline(时间单位为纳秒,由currentTime + delay - startTime计算得出)、state(定时任务当前所处状态,如INIT、CANCELLED、EXPIRED)。

HashedWheelTimeout支持的操作有限,如时钟槽——HashedWheelBucket,它是一个用于缓存和管理定时任务的双向链表容器,每个节点即一个定时任务。持有链表的首尾两个节点,可以完成相关操作。

时钟引擎——HashedWheelTimer有节律地周期性运作,根据当前时钟滴答选定对应时钟槽,从链表头部开始迭代,对每个任务计算是否属于当前时钟周期,属于则执行,否则执行减一操作。

时钟引擎维持两个缓存定时任务的阻塞队列,一个用于接收外界提交的任务,另一个用于缓存主动取消的任务。引擎需要在滴答开始期间将它们装入对应的时钟槽或从中移除。

关键属性包括:timeouts和cancelledTimeouts(队列,用于缓存外界提交或取消的任务)、workerState(定时轮当前所处状态,如init、started、shutdown)、startTime(当前定时轮正式开始调度任务的时间)、ticks(滴答,步长为1的单调递增计数)、ticksDuration(滴答时长)、pendingTimeouts(当前定时轮实时任务剩余数)、n(时钟轮槽数)、mask(掩码)。

引擎内核——Worker,时钟引擎分为对外接口和调度运行两部分,可以想象内核是引擎的心脏起搏器,驱动定时轮运行,完成任务调度。实现上对应一个工作线程。

内核状态包括:状态机是关键组成部分,因此状态值控制对其至关重要。内核状态由定时轮维护管理,对外提供的接口都要借助它实现。初始时为init状态,当引擎被设计为不可复活时,不存在init/started/shutdown → init这样的迁移过程。

外部接口包括:start(用于定时轮开启引擎)、stop(完成定时轮引擎的关闭过程,返回未被处理的定时任务)、Timeout newTimeout(用于向引擎提交任务)。

调度运行:简单而言,就是周期性地执行滴答操作,包括相邻两个滴答周期的开始时间理论上等距,但结束时间会随该周期所需处理任务的数目及时长变化。因此,引擎剩下的休眠时间需要使用特定公式获得。

定时轮在dubbo中的应用:实际上,定时轮算法并不直接用于等周期性地执行某些提交任务,向其提交的任务只会到期执行一次。但具体应用中,会利用每次任务的执行,调用newTimeout()提交Timer所引用的当前任务,使其在若干单位时间后重新继续执行。

Dubbo中对定时轮的应用主要体现在以下几个方面:定时轮用单一的线程去管理触发Task的运行,Task执行期间,不能直接抛异常,否则会导致整个定时轮引擎崩溃而使得提交的后续任务无法执行。

周期任务:在dubbo中,每个连接被表征为一个Channel通道,dubbo节点间建立连接相互通信,单个节点需要维护和多个连入节点的连接。基本的步骤如下:

失败重试:网络情况的复杂多变性使得一件原本在单机上很轻易的事情,在分布式应用中,为确保某类型的操作能发生可能需要重试多次。
相似回答
大家正在搜