在一个系统中,有 几万个任务 需要在各自的特定时间点触发执行,常见的解决方案包括 时间轮(TimeWheel)、任务队列(DelayQueue)、分布式调度系统 等。下面是几种主要的设计思路:
方案 1:Redis + ZSet(有序集合)
核心思路
• 任务按照触发时间存入 Redis ZSet,以 时间戳作为 score,任务 ID 作为 value。
• 定时任务扫描 ZSet,取出当前时间之前的任务并执行。
实现步骤
- 任务入队
import redis
import time
client = redis.StrictRedis(host='localhost', port=6379, decode_responses=True)
# 添加任务,score 为触发时间戳
task_id = "task_123"
execute_time = int(time.time()) + 60 # 60 秒后执行
client.zadd("task_queue", {task_id: execute_time})
2. 任务调度
while True:
now = int(time.time())
tasks = client.zrangebyscore("task_queue", 0, now) # 获取到期任务
for task in tasks:
print(f"执行任务: {task}")
client.zrem("task_queue", task) # 移除已执行任务
time.sleep(1) # 轮询间隔
优点
• 高效:Redis ZSet 按 score 排序,O(logN) 查询最近任务。
• 支持分布式:多个调度器可以并行消费任务。
缺点
• 不适用于严格毫秒级调度(轮询方式有一定延迟)。
• Redis 容量有限,任务量特别大时需要分片存储。
适用场景
• 中等规模(几万任务),执行时间要求秒级的任务调度。
方案 2:DelayQueue(基于 Java 延迟队列)
核心思路
• 使用 Java 的 DelayQueue,任务按照执行时间排序,任务到期后才能被消费。
实现
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
class Task implements Delayed {
private final long executeTime;
private final String taskId;
public Task(String taskId, long delayMillis) {
this.taskId = taskId;
this.executeTime = System.currentTimeMillis() + delayMillis;
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(executeTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(Delayed o) {
return Long.compare(this.executeTime, ((Task) o).executeTime);
}
public void execute() {
System.out.println("执行任务: " + taskId);
}
}
public class TaskScheduler {
private static final DelayQueue<Task> queue = new DelayQueue<>();
public static void main(String[] args) throws InterruptedException {
queue.put(new Task("task_1", 5000)); // 5 秒后执行
while (true) {
Task task = queue.take(); // 任务到期才能取出
task.execute();
}
}
}
优点
• 精准触发,任务不会提前执行。
• 性能较好,Java 原生 DelayQueue 使用最小堆存储,O(logN) 复杂度。
缺点
• 单点运行,无法分布式扩展(适合单机)。
• 任务量过大时,JVM 内存压力大。
适用场景
• 小规模任务调度(单机 10w 任务以内) ,精准毫秒级调度。
方案 3:时间轮(TimeWheel)
核心思路
• 时间轮算法 使用环形结构,把任务分布到不同时间槽中,时间槽依次轮询触发任务,减少遍历成本。
实现
可以使用 Netty HashedWheelTimer:
import io.netty.util.HashedWheelTimer;
import io.netty.util.Timeout;
import io.netty.util.TimerTask;
import java.util.concurrent.TimeUnit;
public class TimeWheelScheduler {
public static void main(String[] args) {
HashedWheelTimer timer = new HashedWheelTimer(100, TimeUnit.MILLISECONDS, 512);
timer.newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) {
System.out.println("执行任务: task_1");
}
}, 5, TimeUnit.SECONDS);
}
}
优点
• 减少 CPU 轮询(相比 DelayQueue)。
• 适合大规模定时任务,支持 10w+ 任务调度。
缺点
• 任务执行时间不精确,存在一定延迟。
适用场景
• 大规模任务(10w+),对精确度要求不高。
方案 4:分布式任务调度(Quartz / XXL-JOB)
如果任务调度需要分布式处理,可以使用 Quartz / XXL-JOB。
核心思路
• 任务存数据库,多个 Worker 分布式执行。
• 基于 Zookeeper / Redis 实现任务协调。
XXL-JOB 示例
@XxlJob("taskJobHandler")
public void execute() {
System.out.println("执行任务: " + System.currentTimeMillis());
}
优点
• 支持集群调度,避免单点故障。
• 任务支持失败重试,提高可靠性。
缺点
• 性能较低,比 Redis / DelayQueue 慢。
适用场景
• 大规模分布式任务调度(10w 以上任务) 。
最佳方案选择
推荐:
• 中等规模(几万任务):Redis + ZSet
• 超大规模(10w+ 任务):分布式任务调度
• 对毫秒级精准度要求高:DelayQueue
• 允许少量延迟(如 100ms 级别):时间轮