基于Netty时间片轮的定时任务

发布于:2025-05-08 ⋅ 阅读:(38) ⋅ 点赞:(0)

netty 的时间片轮实现的定时器与java框架的定时器,有什么区别?时间片轮实现的定时器有什么优势?面对websocket 超大连接量,每个连接的每10秒的定时任务,哪种可以胜任?

🔧 1. Netty 的时间轮定时器(HashedWheelTimer)

Netty 实现的 HashedWheelTimer 使用的是一种时间片轮(Time Wheel)算法来实现高性能定时调度。

🎡 时间轮的基本原理:

  • 类似于时钟,时间被划分为固定长度的槽(slot),每个槽对应一个时间片(tick)。
  • 所有定时任务都按照它们的延时被“安排”到某个槽里。
  • 每 tick 时间轮往前走一步,轮到某个槽时,就执行该槽内到期的任务。

✅ 优点:

  • 极低的内存和 CPU 占用,尤其适合管理大量延迟任务。
  • 插入和删除操作性能优越(接近 O(1))。
  • 适合高并发、海量定时任务调度场景。

❌ 缺点:

  • 时间精度有限,最小粒度是一个 tick(比如 100ms)。
  • 只适合延迟任务调度,不适合固定频率精确任务调度(比如每秒必须执行一次的任务)。

⏲️ 2. Java 框架常见的定时器

主要包括以下几种:

类型

描述

优缺点

java.util.Timer

单线程调度器

精度高,但并发差,任务抛异常会中断所有任务

ScheduledExecutorService

基于线程池调度

支持并发,精度高,适合中低数量的定时任务

Spring TaskScheduler

封装了 ScheduledExecutorService

更易用,适合框架化场景,但并不适合管理海量任务

Quartz

支持复杂 cron 表达式

重型调度器,资源占用大,不适合海量细粒度任务

🧠 3. 你的场景分析:WebSocket 超大连接量 + 每 10 秒定时任务

问题本质:你要管理成千上万的连接,每个都有自己的定时任务(例如:心跳检测、超时处理等)

❌ 不推荐使用:

  • ScheduledExecutorService:成千上万连接 → 成千上万个任务 → 线程资源吃紧。
  • Timer:单线程,不可靠。
  • Quartz:过于重量级,不适合此类场景。

✅ 推荐方案:

使用 Netty 的 HashedWheelTimer

它特别适合这种海量连接 + 延迟任务的场景(比如:每个连接每 10 秒发心跳、检测是否掉线等)。

示例代码:

HashedWheelTimer timer = new HashedWheelTimer();

timer.newTimeout(timeout -> {
    // 你的任务逻辑,例如检查 WebSocket 是否存活
    System.out.println("执行定时任务");

    // 重新调度,模拟周期性任务
    timer.newTimeout(timeout.task(), 10, TimeUnit.SECONDS);
}, 10, TimeUnit.SECONDS);

注:HashedWheelTimer 是延迟任务调度器,不是严格周期性任务。你可以手动递归调度来实现周期性。

⚖️ 总结对比:

特性

HashedWheelTimer

ScheduledExecutorService

性能(海量任务)

高,适合

中等,不适合超大连接

精度

毫秒级(tick 粒度)

毫秒级

并发处理能力

强(非阻塞插入)

线程池受限

适合场景

海量连接、延迟任务

少量定时任务

用例

Netty 连接超时、心跳检测

一般定时业务逻辑

✅ 最佳实践建议:

  • 使用 Netty 的 HashedWheelTimer 管理 WebSocket 心跳/超时检测等每连接周期性任务。
  • 对于少量、关键的定时任务(如统计日志、定时广播),再考虑使用 ScheduledExecutorService

代码实现

PrecisionTaskScheduler.java

import io.netty.util.HashedWheelTimer;
import io.netty.util.Timeout;
import io.netty.util.Timer;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * 封装:基于 HashedWheelTimer 的定时任务调度器
 */
class PrecisionTaskScheduler {
    private final Timer timer;
    private final Map<String, Timeout> activeTasks = new ConcurrentHashMap<>();

    public PrecisionTaskScheduler() {
        this.timer = new HashedWheelTimer(
            r -> {
                Thread t = new Thread(r, "precision-hwt");
                t.setDaemon(true);
                t.setPriority(Thread.MAX_PRIORITY);
                return t;
            },
            1, TimeUnit.MILLISECONDS,
            512
        );
    }

    /**
     * 周期性调度任务(模拟固定频率)
     */
    public void scheduleRecurringTask(String taskId, long intervalSeconds, Runnable task) {
        scheduleNext(taskId, intervalSeconds, task);
    }

    private void scheduleNext(String taskId, long intervalSeconds, Runnable task) {
        Timeout timeout = timer.newTimeout(t -> {
            // 检查任务是否已被取消
            if (!activeTasks.containsKey(taskId)) return;

            try {
                task.run();
            } catch (Exception e) {
                System.err.println("任务异常:" + e.getMessage());
            }

            // 再次调度
            scheduleNext(taskId, intervalSeconds, task);
        }, intervalSeconds, TimeUnit.SECONDS);

        activeTasks.put(taskId, timeout);
    }

    /**
     * 取消任务
     */
    public void cancelTask(String taskId) {
        Timeout timeout = activeTasks.remove(taskId);
        if (timeout != null) {
            timeout.cancel();
        }
    }
}

测试

 public static void main(String[] args) throws InterruptedException {
        PrecisionTaskScheduler scheduler = new PrecisionTaskScheduler();
        // 提交 5 个任务
        for (int i = 0; i < 5; i++) {
            int taskId = i + 1;
            long interval = (i < 3) ? 5 : 2; // 前3个每5秒,后2个每2秒
            scheduler.scheduleRecurringTask("task-" + taskId, interval, () -> {
                System.out.printf("【%s 执行】时间:%s%n", "task-" + taskId, LocalDateTime.now());
            });
        }
        // 示例:10秒后取消 task-2 和 task-4
        Thread.sleep(10_000);
        scheduler.cancelTask("task-2");
        scheduler.cancelTask("task-4");
        System.out.println(">>> 已取消 task-2 和 task-4");

        // 保持主线程
        while (true) {
            Thread.sleep(60_000);
        }
    }


网站公告

今日签到

点亮在社区的每一天
去签到