面试Java后端却问我时间轮算法,面试官没想到我看过Dubbo源码!(下)

HashedWheelTimer

Timer 接口的实现,通过时间轮算法实现了一个定时器。

职能

根据当前时间轮指针选定对应 HashedWheelBucket 槽,从链表头部开始迭代,计算每个 HashedWheelTimeout 定时任务:

  • 属于当前时钟周期则取出运行
  • 不属于则将其剩余的时钟周期数减一

核心域

workerState

时间轮当前所处状态,三个可选值,由 AtomicIntegerFieldUpdater 实现其原子地修改。

面试Java后端却问我时间轮算法,面试官没想到我看过Dubbo源码!(下)startTime

当前时间轮的启动时间,提交到该时间轮的定时任务的 deadline 字段值均以该时间戳为起点进行计算。

面试Java后端却问我时间轮算法,面试官没想到我看过Dubbo源码!(下)

wheel

时间轮环形队列,每个元素都是一个槽。当指定时间轮槽数为 n 时,会向上取最靠近 n 的 2 次幂值

面试Java后端却问我时间轮算法,面试官没想到我看过Dubbo源码!(下)

timeouts、cancelledTimeouts

HashedWheelTimer 会在处理 HashedWheelBucket 的双向链表前,先处理这俩队列的数据:


timeouts 队列

缓冲外部提交时间轮中的定时任务

cancelledTimeouts 队列

暂存取消的定时任务

面试Java后端却问我时间轮算法,面试官没想到我看过Dubbo源码!(下)

tick

位于 HashedWheelTimer$Worker ,时间轮的指针,步长为 1 的单调递增计数器

面试Java后端却问我时间轮算法,面试官没想到我看过Dubbo源码!(下)

mask

掩码, mask = wheel.length - 1,执行 ticks & mask 便能定位到对应的时钟槽

面试Java后端却问我时间轮算法,面试官没想到我看过Dubbo源码!(下)

ticksDuration

时间指针每次加 1 所代表的实际时间,单位为纳秒。

面试Java后端却问我时间轮算法,面试官没想到我看过Dubbo源码!(下)

pendingTimeouts

当前时间轮剩余的定时任务总数。

面试Java后端却问我时间轮算法,面试官没想到我看过Dubbo源码!(下)

workerThread

时间轮内部真正执行定时任务的线程。

面试Java后端却问我时间轮算法,面试官没想到我看过Dubbo源码!(下)

worker

真正执行定时任务的逻辑封装这个 Runnable 对象中。

面试Java后端却问我时间轮算法,面试官没想到我看过Dubbo源码!(下)

newTimeout()

提交定时任务,在定时任务进入到 timeouts 队列之前会先调用 start() 方法启动时间轮,其中会完成下面两个关键步骤:

  1. 确定时间轮的 startTime 字段
  2. 启动 workerThread 线程,开始执行 worker 任务。

之后根据 startTime 计算该定时任务的 deadline,最后才能将定时任务封装成 HashedWheelTimeout 并添加到 timeouts 队列。

4 时间轮指针一次转动的执行流程

HashedWheelTimer$Worker.run()

  1. 时间轮指针转动,时间轮周期开始
  2. 清理用户主动取消的定时任务,这些定时任务在用户取消时,记录到 cancelledTimeouts 队列中。在每次指针转动的时候,时间轮都会清理该队列
  3. 将缓存在 timeouts 队列中的定时任务转移到时间轮中对应的槽中
  4. 根据当前指针定位对应槽,处理该槽位的双向链表中的定时任务
  5. 检测时间轮的状态。如果时间轮处于运行状态,则循环执行上述步骤,不断执行定时任务。如果时间轮处于停止状态,则执行下面的步骤获取到未被执行的定时任务并加入 unprocessedTimeouts 队列:遍历时间轮中每个槽位,并调用 clearTimeouts() 方法;对timeouts 队列中未被加入槽中循环调用 poll()
  1. 最后再次清理 cancelledTimeouts 队列中用户主动取消的定时任务。

5 定时任务应用

并不直接用于周期性操作,而是只向时间轮提交执行单次的定时任务,在上一次任务执行完成的时候,调用 newTimeout() 方法再次提交当前任务,这样就会在下个周期执行该任务。即使在任务执行过程中出现了 GC、I/O 阻塞等情况,导致任务延迟或卡住,也不会有同样的任务源源不断地提交进来,导致任务堆积。


Dubbo 时间轮应用主要在如下方面:

失败重试, 例如,Provider 向注册中心进行注册失败时的重试操作,或是 Consumer 向注册中心订阅时的失败重试等

周期性定时任务, 例如,定期发送心跳请求,请求超时的处理,或是网络连接断开后的重连机制


参考

https://zhuanlan.zhihu.com/p/32906730


上一篇:LAMP+haproxy+varnish实现网站访问的动静分离及静态资源缓存


下一篇:PolarDB-X 1.0-API参考-1.0(2017版本)-OpenAPI使用-SDK 说明