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

1 定时任务

Netty、Quartz、Kafka 以及 Linux 都有定时任务功能。

  • 常规的JDK 的 java.util.Timer 和 DelayedQueue 等工具类,可实现简单的定时任务,底层用的是数据结构,存取复杂度都是 O(nlog(n)),无法支撑海量定时任务。
  • 而在定时任务量大、性能要求高的场景,为将任务存取及取消操作时间复杂度降为 O(1),会使用时间轮方案。

2 时间轮模型及其应用

一种高效批量管理定时任务的调度模型。时间轮一般会实现成一个环形结构,类似一个时钟,分为很多槽,一个槽代表一个时间间隔,每个槽使用双向链表存储定时任务。指针周期性地跳动,跳动到一个槽位,就执行该槽位的定时任务。

  • Hashed Timing Wheel 结构示意图
  • 面试Java后端却问我时间轮算法,面试官没想到我看过Dubbo源码!(上)

适用场景

  • 故障恢复
  • 流量控制
  • 调度算法
  • 控制网络中的数据包生命周期

计时器维护代价高,如果

  • 处理器在每个时钟滴答声中都会中断
  • 使用精细粒度计时器
  • 未完成的计时器很多

需要高效的定时器算法以减少总体中断的开销。

单层时间轮的容量和精度都是有限的,对于精度要求特别高、时间跨度特别大或是海量定时任务需要调度的场景,通常会使用多级时间轮以及持久化存储与时间轮结合的方案。

模型和性能指标

模型中的规则

  • 客户端调用:
    • START_TIMER(时间间隔,Request_ID,Expiry_Action)
    • STOP_TIMER(Request_ID)
  • 计时器tick调用:
    • PER_TICK_BOOKKEEPING
    • EXPIRY_PROCESSING

性能指标

  • 空间
    数据结构使用的内存
  • 延迟
    开始和结束上述任何例程所需的时间

3 Dubbo的时间轮结构

Dubbo 的时间轮实现位于 dubbo-common 模块的 org.apache.dubbo.common.timer 包中,下面我们就来分析时间轮涉及的核心接口和实现。

核心接口

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

TimerTask

在 Dubbo 中,所有定时任务都要实现 TimerTask 接口。只定义了一个 run() 方法,入参是一个 Timeout 接口对象。

Timeout

Timeout 对象与 TimerTask 对象一一对应,类似线程池返回的 Future 对象与提交到线程池中的任务对象之间的关系。

通过 Timeout 对象,不仅可以查看定时任务的状态,还可以操作定时任务(例如取消关联的定时任务)。


Timeout 接口中的方法:

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

Timer 接口定义了定时器的基本行为,核心是 newTimeout() :提交一个定时任务(TimerTask)并返回关联的 Timeout 对象,类似于向线程池提交任务。

HashedWheelTimeout

HashedWheelTimeout 是 Timeout 接口的唯一实现,是 HashedWheelTimer 的内部类。HashedWheelTimeout 扮演了两个角色:

  • 时间轮中双向链表的节点,即定时任务 TimerTask 在 HashedWheelTimer 中的容器
  • 定时任务 TimerTask 提交到 HashedWheelTimer 之后返回的句柄(Handle),用于在时间轮外部查看和控制定时任务
上一篇:谁说程序员不浪漫——给女友放个烟花,安排!安排!


下一篇:fire库的使用