时间轮算法 (Time Wheel) 是解决海量定时任务(Delayed Tasks)管理的核心算法。在 Java 高性能中间件(如 Netty、Kafka、Dubbo)中,它被广泛用于替代传统的 PriorityQueue 或 DelayQueue,以实现极致的性能。
1. 为什么要使用时间轮?
在理解算法之前,先看它解决了什么问题。
传统的定时任务实现(如 java.util.Timer 或 ScheduledThreadPoolExecutor)通常依赖于 最小堆 (Min-Heap) 或 优先级队列。
- 原理: 将所有任务按执行时间排序,每次取堆顶(最早到期)的任务。
- 瓶颈: 插入和删除任务的时间复杂度是 O(log n)。当任务数量(n)达到百万级别时,频繁的入队出队会带来巨大的性能损耗。
时间轮的优势:
- 复杂度: 任务的添加和取消通常可以做到 O(1) 或 O(m)(m 为轮的层级,通常很小)。
- 场景: 特别适合高并发、海量、短延迟的定时任务(例如:心跳检测、请求超时控制)。
2. 核心原理:机械钟表的抽象
想象一个老式的机械挂钟。时间轮利用了环形数组来模拟这个钟表。
核心组件
- Wheel (环形数组): 一个固定大小的数组(Bucket/Slot),数组的每个元素代表一个时间刻度。
- Tick (指针跳动): 指针每隔固定的时间(
tickDuration)向前移动一格。 - Slot (槽位): 数组中的每一格。如果多个任务在同一时刻执行,它们会以双向链表的形式挂在同一个 Slot 中。
运作流程
假设一个时间轮有 8 个槽位(0-7),tickDuration 为 1 秒。
- 当前指针指向 Slot 0。
- 添加任务: 现在需要添加一个'5 秒后执行'的任务。
- 计算位置:
(CurrentPos + 5) % 8 = 5。 - 将任务插入 Slot 5 的链表中。
- 计算位置:
- 推进时间:
- 第 1 秒,指针移到 Slot 1,检查链表是否有任务,执行并清除。
- …
- 第 5 秒,指针移到 Slot 5,发现刚才的任务,取出执行。
3. 进阶:如何处理'长延迟'任务?
如果轮子只有 8 格(8 秒一圈),但我要添加一个 50 秒后 执行的任务,怎么办?
这里有两种主流的解决方案:
方案 A:带圈数的时间轮 (Netty 策略)
这是 Netty 的 HashedWheelTimer 使用的方式。
- 原理: 给每个任务增加一个
rounds(圈数)变量。 - 计算:
- 50 秒后执行,轮子一圈 8 秒。
50 / 8 = 6圈,余 2。- 任务放入 Slot
(Current + 2),并将任务的rounds设为 6。

