HashedWheelTimer时间轮原理分析

HashedWheelTimer是Netty实现的一种优化I/O超时检测的定时器,基于时间轮数据结构,常用于心跳检测、超时管理等场景。其通过多层时间轮提高精度并减少空转,适合对时效性要求不高的大量小任务。缺点是任务串行执行,当任务执行时间过长可能影响其他任务。对比其他延迟任务方案,如DelayQueue、ScheduledExecutorService,HashedWheelTimer在性能和资源消耗上有优势。
摘要由CSDN通过智能技术生成

概要

时间轮是一种非常惊艳的数据结构。其在Linux内核中使用广泛,是Linux内核定时器的实现方法和基础之一。Netty内部基于时间轮实现了一个HashedWheelTimer来优化I/O超时的检测,本文将详细分析HashedWheelTimer的使用及原理。

背景

由于Netty动辄管理100w+的连接,每一个连接都会有很多超时任务。比如发送超时、心跳检测间隔等,如果每一个定时任务都启动一个Timer,不仅低效,而且会消耗大量的资源。
在Netty中的一个典型应用场景是判断某个连接是否idle,如果idle(如客户端由于网络原因导致到服务器的心跳无法送达),则服务器会主动断开连接,释放资源。得益于Netty NIO的优异性能,基于Netty开发的服务器可以维持大量的长连接,单台8核16G的云主机可以同时维持几十万长连接,及时掐掉不活跃的连接就显得尤其重要。

看看官方文档说明:

A optimized for approximated I/O timeout scheduling.
You can increase or decrease the accuracy of the execution timing by

  • specifying smaller or larger tick duration in the constructor. In most
  • network applications, I/O timeout does not need to be accurate. Therefore,
  • the default tick duration is 100 milliseconds and you will not need to try
  • different configurations in most cases.

大概意思是一种对“适当”I/O超时调度的优化。因为I/O timeout这种任务对时效性不需要准确。

这种方案也不是Netty凭空造出来的,而是根据George Varghese和Tony Lauck在1996年的论文实现的,有兴趣的可以阅读一下。
论文下载
论文PPT

应用场景

HashedWheelTimer本质是一种类似延迟任务队列的实现,那么它的特点就是上述所说的,适用于对时效性不高的,可快速执行的,大量这样的“小”任务,能够做到高性能,低消耗。
例如:

  • 心跳检测
  • session、请求是否timeout
    业务场景则有:
  • 用户下单后发短信
  • 下单之后15分钟,如果用户不付款就自动取消订单

简单使用

如果之前没用过,先看看用法有一个大体的感受,

@Slf4j
public class HashedWheelTimerTest {

    private CountDownLatch countDownLatch = new CountDownLatch(2);

    @Test
    public void test1() throws Exception {
        //定义一个HashedWheelTimer,有16个格的轮子,每一秒走一个一个格子
        HashedWheelTimer timer = new HashedWheelTimer(1, TimeUnit.SECONDS, 16);
        //把任务加到HashedWheelTimer里,到了延迟的时间就会自动执行
        timer.newTimeout((timeout) -> {
            log.info("task1 execute");
            countDownLatch.countDown();
        }, 500, TimeUnit.MILLISECONDS);
        timer.newTimeout((timeout) -> {
            log.info("task2 execute");
            countDownLatch.countDown();
        }, 2, TimeUnit.SECONDS);
        countDownLatch.await();
        timer.stop();
    }
}

需要引入netty-all.jar
使用上跟ScheduledExecutorService差不多。

实现原理

源码基于netty-all.4.1.34.Final

数据结构

时间轮其实就是一种环形的数据结构,可以想象成时钟,分成很多格子,一个格子代码一段时间(这个时间越短,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一圈。

对于HashedWheelTimer的数据结构在介绍完源码之后有图解。

初始化

HashedWheelTimer整体代码不难,慢慢看应该都可以看懂
我们从HashedWheelTimer的构造方法入手,先说明一下

构造方法

//附上文档说明,自行阅读
/**
 * Creates a new timer.
 *
 * @param threadFactory        a {@link ThreadFactory} that creates a
 *                             background {@link Thread} which is dedicated to
 *                             {@link TimerTask} execution.
 * @param tickDuration         the duration between tick
 * @param unit                 the time unit of the {@code tickDuration}
 * @param ticksPerWheel        the size of the wheel
 * @param leakDetection        {@code true} if leak detection should be enabled always,
 *                             if false it will only be enabled if the worker thread is not
 *                             a daemon thread.
 * @param  maxPendingTimeouts  The maximum number of pending timeouts after which call to
 *                             {@code newTimeout} will result in
 *                             {@link java.util.concurrent.RejectedExecutionException}
 *                        
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值