时间轮(Time Wheel)简介
时间轮是一种高效的定时任务调度算法,特别适合处理海量定时任务。它的核心思想是将时间划分为多个槽(slot),每个槽代表一个时间间隔。任务根据其延迟时间被分配到相应的槽中,时间轮按照固定的时间间隔推进,执行当前槽中的任务。
时间轮的优点:
- 高效:任务调度的时间复杂度为 O(1)。
- 低内存占用:通过循环利用时间槽,减少内存开销。
- 适合海量任务:能够轻松处理大量定时任务。
时间轮的工作原理
-
时间槽(Slot):
- 时间轮被划分为多个槽,每个槽代表一个时间间隔(例如 1 秒)。
- 每个槽中存储一个任务链表,链表中存放需要在当前时间点执行的任务。
-
指针(Pointer):
- 时间轮有一个指针,指向当前时间槽。
- 指针按照固定的时间间隔(例如 1 秒)向前移动。
-
任务分配:
- 当一个任务需要被调度时,计算其延迟时间,并将其分配到对应的槽中。
- 如果任务的延迟时间超过时间轮的总范围,可以通过多级时间轮(Hierarchical Time Wheel)来处理。
-
任务执行:
- 当指针移动到某个槽时,执行该槽中的所有任务。
使用时间轮实现海量定时任务
下面是一个简单的 Java 实现,展示如何使用时间轮调度定时任务。
1. 定义时间轮结构
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class TimeWheel {
private final int slotNum; // 时间槽数量
private final long interval; // 每个槽的时间间隔(毫秒)
private final List<Runnable>[] slots; // 时间槽数组
private int currentSlot; // 当前指针位置
private final ScheduledExecutorService scheduler; // 调度器
public TimeWheel(int slotNum, long interval) {
this.slotNum = slotNum;
this.interval = interval;
this.slots = new List[slotNum];
for (int i = 0; i < slotNum; i++) {
slots[i] = new LinkedList<>();
}
this.currentSlot = 0;
this.scheduler = Executors.newScheduledThreadPool(1);
start();
}
// 启动时间轮
private void start() {
scheduler.scheduleAtFixedRate(() -> {
// 执行当前槽中的任务
List<Runnable> tasks = slots[currentSlot];
for (Runnable task : tasks) {
task.run();
}
tasks.clear(); // 清空当前槽
currentSlot = (currentSlot + 1) % slotNum; // 移动指针
}, interval, interval, TimeUnit.MILLISECONDS);
}
// 添加任务
public void addTask(Runnable task, long delay) {
if (delay <= 0) {
task.run();
return;
}
int targetSlot = (currentSlot + (int) (delay / interval)) % slotNum;
slots[targetSlot].add(task);
}
// 停止时间轮
public void stop() {
scheduler.shutdown();
}
}
2. 使用时间轮调度任务
public class TimeWheelDemo {
public static void main(String[] args) throws InterruptedException {
// 创建一个时间轮,10 个槽,每个槽 1 秒
TimeWheel timeWheel = new TimeWheel(10, 1000);
// 添加任务
timeWheel.addTask(() -> System.out.println("Task 1 executed at " + System.currentTimeMillis()), 3000); // 3 秒后执行
timeWheel.addTask(() -> System.out.println("Task 2 executed at " + System.currentTimeMillis()), 5000); // 5 秒后执行
timeWheel.addTask(() -> System.out.println("Task 3 executed at " + System.currentTimeMillis()), 7000); // 7 秒后执行
// 等待任务执行
Thread.sleep(10000);
// 停止时间轮
timeWheel.stop();
}
}
多级时间轮(Hierarchical Time Wheel)
对于更长的延迟时间(例如几天或几个月),单级时间轮可能不够用。此时可以使用多级时间轮,将时间划分为多个层级,每个层级处理不同的时间范围。
示例:
- 第一级时间轮:1 秒一个槽,共 60 个槽,处理 1 分钟内的任务。
- 第二级时间轮:1 分钟一个槽,共 60 个槽,处理 1 小时内的任务。
- 第三级时间轮:1 小时一个槽,共 24 个槽,处理 1 天内的任务。
当任务从高级时间轮降级到低级时间轮时,重新分配到对应的槽中。
总结
时间轮是一种高效的定时任务调度算法,适合处理海量定时任务。通过单级或多级时间轮,可以实现从毫秒到天级别的任务调度。在实际应用中,时间轮被广泛用于网络框架、分布式任务调度等场景。