--- date: 2021-03-02 --- [toc] # 背景 HashedWheelTimer 本质是一种类似延迟任务队列的实现,适用于对时效性不高的,可快速执行的,大量这样的 “小” 任务,能够做到高性能,低消耗。 时间轮是一种非常惊艳的数据结构。其在 Linux 内核中使用广泛,是 Linux 内核定时器的实现方法和基础之一。Netty 内部基于时间轮实现了一个 HashedWheelTimer 来优化 I/O 超时的检测。 因为 Netty 需要管理上万的连接,每个连接又会有发送超时、心跳检测等,如果都使用 Timer 定时器的话,将耗费大量的资源。 在 Netty 中的一个典型应用场景是判断某个连接是否 idle,如果 idle(如客户端由于网络原因导致到服务器的心跳无法送达),则服务器会主动断开连接,释放资源。得益于 Netty NIO 的优异性能,基于 Netty 开发的服务器可以维持大量的长连接,单台 8 核 16G 的云主机可以同时维持几十万长连接,及时掐掉不活跃的连接就显得尤其重要。 # 延迟任务方案都有哪些?优缺点? - ` 数据库轮询 `:数据先保存在数据库中,然后启动一个定时任务,根据时间和状态将待完成的任务数据捞出来,处理完成后再更新数据库。这种方法比较简洁,但是依赖数据库,同时如果任务数据量很大(千万)且的话,会存在数据库读写性能问题,且数据库读写可能占用大量时间,甚至超过任务处理的时间。有点是数据可以持久化,服务重启不丢失,并且可以查询管理未完成的任务。 - `DelayQueue` 本质是一个 PriorityQueue,每次插入和删除都调整堆,时间复杂度是 O(longN),而 HashedWheelTimer 的时间复杂度是 O(1)。 - `ScheduledExecutorService`,JDK 的 ScheduledExecutorService 本质上仍然是一个 DelayQueue,但是任务是通过多线程的方式进行。 # 源码分析 ## 使用示例 源码分析首先通过一个 ` 使用示例 ` 开始,HashedWheelTimer 一个典型的使用方法如下: ```java @Test public void test() throws InterruptedException { HashedWheelTimer wheelTimer = new HashedWheelTimer(); wheelTimer.newTimeout(timeout -> System.out.println("1s delay"), 1, TimeUnit.SECONDS); wheelTimer.newTimeout(timeout -> System.out.println("10s delay"), 10, TimeUnit.SECONDS); wheelTimer.newTimeout(timeout -> System.out.println("11s delay"), 11, TimeUnit.SECONDS); TimeUnit.SECONDS.sleep(20); } ``` 在新建一个 HashedWheelTimer 对象实例后,可以向里面添加一个延迟任务,需要指定任务 TimerTask,延迟时间。 ## DOC 文档 官方的 4.0 版本的 doc 文档:[https://netty.io/4.0/api/io/netty/util/HashedWheelTimer.html](https://netty.io/4.0/api/io/netty/util/HashedWheelTimer.html)
Timer optimized for approximated I/O timeout scheduling.
TimerTask on time. HashedWheelTimer, on every tick, will
check if there are any TimerTasks behind the schedule and execute
them.
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.
HashedWheelTimer maintains a data structure called 'wheel'.
To put simply, a wheel is a hash table of TimerTasks whose hash
function is 'dead line of the task'. The default number of ticks per wheel
(i.e. the size of the wheel) is 512. You could specify a larger value
if you are going to schedule a lot of timeouts.
HashedWheelTimer creates a new thread whenever it is instantiated and
started. Therefore, you should make sure to create only one instance and
share it across your application. One of the common mistakes, that makes
your application unresponsive, is to create a new instance for every connection.
HashedWheelTimer is based on
George Varghese and
Tony Lauck's paper,
'Hashed
and Hierarchical Timing Wheels: data structures to efficiently implement a
timer facility'. More comprehensive slides are located
here.