时间轮算法HashedWheelTimer

一.HashedWheelTimer是什么?

   时间轮是一种非常惊艳的数据结构。其在Linux内核中使用广泛,是Linux内核定时器的实现方法和基础之一。
   换句话说时间轮是一种高效来利用线程资源来进行批量化调度的一种调度模型。把大批量的调度任务全部都绑定到同一个的调度器上面,使用这一个调度器来进行所有任务的管理(manager),触发(trigger)以及运行(runnable)。能够高效的管理各种延时任务,周期任务,通知任务等等
   而HashedWheelTimer则是使用了时间轮这种数据结构,它是Netty内部的一个工具类,最开始主要用来优化I/O超时的检测,本文将详细分析HashedWheelTimer的使用及原理。

二.能干什么?为什么需要这个东西?

优点

      其实笔者认为其最大的优点就是可以在一个线程中动态的添加定时(延时)任务
像我们经常使用Timer,ScheduledExecutorService,Spring的Scheduled这些都是无法做到这一点的,一旦某个线程开始执行某个定时任务,都是无法再去动态添加的

      那某些场景,比如说有很多小的定时任务,难道每一个都去起一个线程处理吗?那数量多的话对程序势必影响很大,浪费资源,这个时候就可以考虑HashedWheelTimer了.

      而在netty中,因为其可能管理上百万的连接,每一个连接都会有很多超时任务。比如发送超时、心跳检测间隔等,如果每一个定时任务都启动一个Timer,不仅低效,而且会消耗大量的资源。所以创造了这个工具类.

适用场景

  1. 订单超时
  2. 分布式锁中为线程续期的看门狗
  3. 心跳检测

三.怎么用?使用步骤

1.引入pom

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.45.Final</version>
</dependency>

2.使用举例

1.构建对象,添加定时任务

        //在一个格子里面的并不会区分的很细,而会依次顺序执行,所以适用于对时间精度要求不高的任务
        //构建时间轮对象
        HashedWheelTimer timer = new HashedWheelTimer(5, TimeUnit.SECONDS, 10);
        //添加定时任务1,延迟2s执行
        timer.newTimeout((TimerTask) timeout -> {
            System.out.println("任务1执行");
            System.out.println("线程名称:"+Thread.currentThread().getName());

        },2,TimeUnit.SECONDS);
        //添加定时任务2,延迟2s执行
        timer.newTimeout((TimerTask) timeout -> {
            System.out.println("任务2执行");
            System.out.println("线程名称:"+Thread.currentThread().getName());
        },5,TimeUnit.SECONDS);

        //等待定时任务执行完毕后,将时间轮内部工作线程停止,这里只是粗略的等待,也可以使用CountDownLatch
        Thread.sleep(10000);

        timer.stop();

2.取消某个定时任务

       //构建时间轮对象
        HashedWheelTimer timer = new HashedWheelTimer(1, TimeUnit.SECONDS, 10);
        //添加定时任务1
        Timeout newTimeout = timer.newTimeout((TimerTask) timeout -> {
            System.out.println("任务1执行");
            System.out.println("线程名称:" + Thread.currentThread().getName());

        }, 5, TimeUnit.SECONDS);

        //现在又想取消掉这个任务
        if(!newTimeout.isExpired()){
            newTimeout.cancel();
        }

四.时间轮原理

   时间轮其实就是一种环形的数据结构,可以想象成时钟,分成很多格子,一个格子代码一段时间(这个时间越短,Timer的精度越高)。并用一个链表表示在该格子上的到期任务,同时一个指针随着时间一格一格转动,并执行相应格子中的到期任务。任务通过取摸决定放入那个格子。如下图所示:
在这里插入图片描述

   假设一个格子是1秒,则整个wheel能表示的时间段为8s,假如当前指针指向2,此时需要调度一个3s后执行的任务,显然应该加入到(2+3=5)的方格中,指针再走3次就可以执行了;如果任务要在10s后执行,应该等指针走完一个round零2格再执行,因此应放入4,同时将round(1)保存到任务中。检查到期任务时应当只执行round为0的,格子上其他任务的round应减1。

再回头看看构造方法的三个参数分别代表

  • tickDuration
    每一tick的时间
  • timeUnit
    tickDuration的时间单位
  • ticksPerWheel
    就是轮子一共有多个格子,即要多少个tick才能走完这个wheel一圈。

五.使用注意点

1.一个HashedWheelTimer对象只有一个worker线程

2.每次添加的任务只会执行一次

3.时间轮的参数非常重要

   比如我这里设置每个格子的时间为6s,添加了两个定时任务,一个延时2s执行,一个延时5s执行,但是最终执行结果是同时执行,因为这两个任务都被分配到第一个格子中,按顺序执行.
   所以时间轮的参数要根据时间情况具体设定

在这里插入图片描述

4.所有的任务都是顺序串行执行的

也就是说上一个任务的异常延时会影响到下一个任务.
比如我这里添加了两个定时任务,第一个延时2s,第二个延时5s,但是因为第一个任务的延时,导致第二个延时了10s才执行.
在这里插入图片描述
所以这里要求时间轮执行的任务都是比较快的, 或者这里可以使用异步任务去处理.

今天的分享就到这里了,有问题可以在评论区留言,均会及时回复呀.
我是bling,未来不会太差,只要我们不要太懒就行, 咱们下期见.
在这里插入图片描述

<think>好的,我需要帮助用户了解如何在Spring Boot中使用Netty的HashedWheelTimer时间轮。首先,我应该回忆HashedWheelTimer的基本概念和它在Netty中的作用。时间轮是一种高效的任务调度算法,适用于大量的定时任务,Netty的实现通过哈希轮来管理这些任务,减少同步开销和调度压力。 接下来,用户提到要结合Spring Boot,所以需要考虑如何在Spring Boot项目中集成Netty的HashedWheelTimer。可能需要添加Netty的依赖到项目的pom.xml或者build.gradle文件中。这里要确保使用正确的Netty版本,避免兼容性问题。 然后,HashedWheelTimer的实例化是关键步骤。在Spring中,通常使用配置类来创建Bean,所以应该创建一个配置类,比如NettyConfig,并在其中通过@Bean注解来提供HashedWheelTimer的实例。这里需要考虑线程工厂、时间轮的刻度大小、每个刻度的时间单位以及是否启动守护线程等参数配置。 接下来是编写具体的定时任务。用户可能需要一个服务类,比如TaskService,在初始化时注入HashedWheelTimer,并添加定时任务。这里需要展示如何创建TimeoutTask,并通过newTimeout方法提交任务到时间轮中。要注意任务执行的逻辑,以及可能的异常处理。 还需要考虑任务的取消机制,比如如何保存Timeout实例以便后续取消任务。可能需要使用ConcurrentHashMap来管理这些任务,避免内存泄漏。 另外,要提醒用户正确关闭HashedWheelTimer,以释放资源。在Spring的销毁方法中调用stop()方法,确保应用结束时时间轮停止运行。 最后,可能需要给出一个完整的示例,包括配置类、服务类以及一个简单的控制器来触发任务。同时,需要指出常见问题,比如时间精度不够高、任务执行时间过长影响后续任务等,以及解决方案,比如使用更细的时间刻度或将耗时任务放入线程池处理。 检查过程中要确保代码示例正确,依赖项正确,并且步骤清晰。同时,要使用中文回答,避免使用Markdown格式,但按照用户的要求,系统级指令需要处理数学表达式,但这个问题中可能不需要,所以可以忽略。但需要确保LaTeX语法正确,不过当前问题可能不涉及数学公式,所以主要关注代码和配置的正确性。</think>在Spring Boot中使用Netty的`HashedWheelTimer`时间轮,可以通过以下步骤实现: --- ### 一、基础概念 **HashedWheelTimer**是Netty实现的高效延时任务调度器,核心特点: - 使用哈希轮算法管理任务 - 单线程处理任务调度 - 适合大量短周期定时任务(毫秒/秒级) - 时间复杂度:$O(1)$ 添加/取消任务 --- ### 二、Spring Boot集成步骤 #### 1. 添加依赖 ```xml <dependency> <groupId>io.netty</groupId> <artifactId>netty-common</artifactId> <version>4.1.100.Final</version> <!-- 使用最新稳定版 --> </dependency> ``` #### 2. 创建时间轮Bean ```java @Configuration public class NettyConfig { @Bean(destroyMethod = "stop") public HashedWheelTimer hashedWheelTimer() { // 参数说明: // tickDuration: 时间刻度间隔(默认100ms) // ticksPerWheel: 时间轮刻度数量(默认512) return new HashedWheelTimer( new DefaultThreadFactory("netty-timer"), 100, TimeUnit.MILLISECONDS, 512 ); } } ``` #### 3. 编写任务服务 ```java @Service public class TimerTaskService { private final HashedWheelTimer timer; private final ConcurrentMap<String, Timeout> taskMap = new ConcurrentHashMap<>(); @Autowired public TimerTaskService(HashedWheelTimer timer) { this.timer = timer; } /** * 添加延时任务 * @param taskId 任务ID * @param delay 延迟时间 * @param timeUnit 时间单位 * @param task 任务逻辑 */ public void addTask(String taskId, long delay, TimeUnit timeUnit, Runnable task) { Timeout timeout = timer.newTimeout(new TimerTask(taskId, task), delay, timeUnit); taskMap.put(taskId, timeout); } /** * 取消任务 * @param taskId 任务ID */ public boolean cancelTask(String taskId) { Timeout timeout = taskMap.remove(taskId); if (timeout != null) { timeout.cancel(); return true; } return false; } private static class TimerTask implements TimerTaskHandler { private final String taskId; private final Runnable task; TimerTask(String taskId, Runnable task) { this.taskId = taskId; this.task = task; } @Override public void run(Timeout timeout) { try { task.run(); } finally { // 任务完成后自动移除 taskMap.remove(taskId); } } } } ``` #### 4. 控制器示例 ```java @RestController @RequestMapping("/timer") public class TimerController { @Autowired private TimerTaskService timerTaskService; @PostMapping("/schedule") public String scheduleTask(@RequestParam String taskId, @RequestParam long delay) { timerTaskService.addTask(taskId, delay, TimeUnit.SECONDS, () -> { // 实际任务逻辑 System.out.println("执行任务:" + taskId + " 时间:" + LocalDateTime.now()); }); return "Task scheduled: " + taskId; } @PostMapping("/cancel") public String cancelTask(@RequestParam String taskId) { return timerTaskService.cancelTask(taskId) ? "Task canceled" : "Task not found"; } } ``` --- ### 三、关键配置参数建议 | 参数 | 推荐值 | 说明 | |------|--------|-----| | `tickDuration` | 10-100ms | 时间轮转动间隔 | | `ticksPerWheel` | 512-1024 | 时间轮槽位数 | | `maxPendingTimeouts` | 10,000 | 最大等待任务数(需通过构造参数设置) | --- ### 四、注意事项 1. **任务执行时间**:避免单个任务执行时间超过`tickDuration`,否则会影响后续任务 2. **精度问题**:实际执行时间可能有±1个时间刻度的误差 3. **资源释放**:Spring关闭时需调用`timer.stop()` 4. **耗时任务**:建议在任务内部使用线程池处理 5. **任务持久化**:需要自行实现任务恢复机制 --- ### 五、高级用法示例 ```java // 创建可暂停的时间轮 HashedWheelTimer timer = new HashedWheelTimer(); timer.start(); // 需要手动启动 // 暂停所有任务 timer.stop(); // 恢复运行 timer.start(); ``` --- 通过以上实现,可以在Spring Boot应用中高效管理定时任务,适合订单超时处理、缓存过期等场景。实际生产环境中建议配合监控系统使用,统计任务执行情况。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员bling

义父,感谢支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值