Kafka、Dubbo、ZooKeeper、Netty、Caffeine、Akka 中都有对时间轮的实现。
时间轮简单来说就是一个环形的队列(底层一般基于数组实现),队列中的每一个元素(时间格)都可以存放一个定时任务列表。
时间轮中的每个时间格代表了时间轮的基本时间跨度或者说时间精度,假如时间一秒走一个时间格的话,那么这个时间轮的最高精度就是 1 秒(也就是说 3 s 和 3.9s 会在同一个时间格中)。
下图是一个有 12 个时间格的时间轮,转完一圈需要 12 s。当我们需要新建一个 3s 后执行的定时任务,只需要将定时任务放在下标为 3 的时间格中即可。当我们需要新建一个 9s 后执行的定时任务,只需要将定时任务放在下标为 9 的时间格中即可。
那当我们需要创建一个 13s 后执行的定时任务怎么办呢?这个时候可以引入一叫做 圈数/轮数 的概念,也就是说这个任务还是放在下标为 1 的时间格中, 不过它的圈数为 2 。
除了增加圈数这种方法之外,还有一种 多层次时间轮 (类似手表),Kafka 采用的就是这种方案。
上图的时间轮,第 1 层的时间精度为 1 ,第 2 层的时间精度为 20 ,第 3 层的时间精度为 400。假如我们需要添加一个 350s 后执行的任务 A 的话(当前时间是 0s),这个任务会被放在第 2 层(因为第二层的时间跨度为 20*20=400>350)的第 350/20=17 个时间格子。
当第一层转了 17 圈之后,时间过去了 340s ,第 2 层的指针此时来到第 17 个时间格子。此时,第 2 层第 17 个格子的任务会被移动到第 1 层。
任务 A 当前是 10s 之后执行,因此它会被移动到第 1 层的第 10 个时间格子。
时间轮比较适合任务数量比较多的定时任务场景,它的任务写入和执行的时间复杂度都是 0(1)。
时间轮算法具有以下优点:
- 高效性:时间轮算法能够快速地查找和执行定时任务,特别适用于大量定时任务的情况。
- 公平性:时间轮算法对所有定时任务都是公平的,每个定时任务都有机会在时间轮上获得一个槽位。
- 扩展性:时间轮算法可以容易地扩展到多处理器系统中,只需要将时间轮上的槽位扩展到多个处理器上即可。
然而,时间轮算法也存在一些缺点:
- 时间粒度:时间轮算法的时间粒度是固定的,不能灵活地处理不同时间精度的定时任务。
- 精度问题:由于时间轮算法是使用循环队列来跟踪时间,因此可能会出现“假溢出”现象,即指针在还没有到达下一个槽位时就又回到了起点,这会影响定时任务的精度。
- CPU 利用率:时间轮算法需要不断地更新指针并执行定时任务,这会占用大量的 CPU 资源。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。