docs: change to relative path

This commit is contained in:
yanglbme 2020-10-20 10:32:07 +08:00
parent 26cdc41640
commit 822b61c14b
9 changed files with 347 additions and 329 deletions

View File

@ -1,6 +1,7 @@
# 前言 # 前言
前段时间在给自己的玩具项目设计的时候就遇到了一个场景需要定时任务,于是就趁机了解了目前主流的一些定时任务方案,比如下面这些: 前段时间在给自己的玩具项目设计的时候就遇到了一个场景需要定时任务,于是就趁机了解了目前主流的一些定时任务方案,比如下面这些:
- Timerhalo 博客源码中用到了) - Timerhalo 博客源码中用到了)
- ScheduledExecutorService - ScheduledExecutorService
- ThreadPoolTaskScheduler基于 ScheduledExecutorService - ThreadPoolTaskScheduler基于 ScheduledExecutorService
@ -9,6 +10,7 @@
- Kafka 的 TimingWheel层级时间轮 - Kafka 的 TimingWheel层级时间轮
还有一些分布式的定时任务: 还有一些分布式的定时任务:
- Quartz - Quartz
- xxl-job我实习公司就在用这个 - xxl-job我实习公司就在用这个
- ... - ...
@ -19,13 +21,14 @@
# HashedWheelTimer 实现图示 # HashedWheelTimer 实现图示
![HashedWheelTimer实现图示.png](https://www.wenjie.store/blog/img/image_1595752125587.png) ![HashedWheelTimer实现图示.png](../../../images/Netty/image_1595752125587.png)
大致有个理解就行,关于蓝色格子中的数字,其实就是剩余时钟轮数,这里听不懂也没关系,等后面看到源码解释就懂了~~(大概)~~。 大致有个理解就行,关于蓝色格子中的数字,其实就是剩余时钟轮数,这里听不懂也没关系,等后面看到源码解释就懂了~~(大概)~~。
# HashedWheelTimer 简答使用例子 # HashedWheelTimer 简答使用例子
这里顺便列出 schedule 的使用方式,下面是某个 Handler 中的代码: 这里顺便列出 schedule 的使用方式,下面是某个 Handler 中的代码:
```java ```java
@Override @Override
public void handlerAdded(final ChannelHandlerContext ctx) { public void handlerAdded(final ChannelHandlerContext ctx) {
@ -43,17 +46,17 @@
} }
``` ```
# HashedWheelTimer 源码 # HashedWheelTimer 源码
### 继承关系、方法 ### 继承关系、方法
![继承关系&方法.png](https://github.com/mimajiushi/img/blob/master/netty/image_1595751597062.png) ![继承关系&方法.png](../../../images/Netty/image_1595751597062.png)
### 构造函数、属性 ### 构造函数、属性
请记住这些属性的是干啥用的,后面会频繁遇到: 请记住这些属性的是干啥用的,后面会频繁遇到:
`io.netty.util.HashedWheelTimer#HashedWheelTimer(java.util.concurrent.ThreadFactory, long, java.util.concurrent.TimeUnit, int, boolean, long)` `io.netty.util.HashedWheelTimer#HashedWheelTimer(java.util.concurrent.ThreadFactory, long, java.util.concurrent.TimeUnit, int, boolean, long)`
```java ```java
public HashedWheelTimer( public HashedWheelTimer(
ThreadFactory threadFactory, ThreadFactory threadFactory,
@ -123,6 +126,7 @@
添加定时任务其实就是 Timer 接口的 newTimeOut 方法: 添加定时任务其实就是 Timer 接口的 newTimeOut 方法:
`io.netty.util.HashedWheelTimer#newTimeout` `io.netty.util.HashedWheelTimer#newTimeout`
```java ```java
@Override @Override
public Timeout newTimeout(TimerTask task, long delay, TimeUnit unit) { public Timeout newTimeout(TimerTask task, long delay, TimeUnit unit) {
@ -172,6 +176,7 @@
这里我们再跟进 start 方法看看: 这里我们再跟进 start 方法看看:
`io.netty.util.HashedWheelTimer#start` `io.netty.util.HashedWheelTimer#start`
```java ```java
public void start() { public void start() {
switch (WORKER_STATE_UPDATER.get(this)) { switch (WORKER_STATE_UPDATER.get(this)) {
@ -205,6 +210,7 @@
定时任务的执行逻辑其实就在 Worker 的 run 方法中: 定时任务的执行逻辑其实就在 Worker 的 run 方法中:
`io.netty.util.HashedWheelTimer.Worker#run` `io.netty.util.HashedWheelTimer.Worker#run`
```java ```java
// 用于处理取消的任务 // 用于处理取消的任务
private final Set<Timeout> unprocessedTimeouts = new HashSet<Timeout>(); private final Set<Timeout> unprocessedTimeouts = new HashSet<Timeout>();
@ -266,10 +272,12 @@
processCancelledTasks(); processCancelledTasks();
} }
``` ```
- 取消任务的逻辑这里就不展开看了,也比较简单,有兴趣自行补充即可。 - 取消任务的逻辑这里就不展开看了,也比较简单,有兴趣自行补充即可。
看看上面的 transferTimeoutsToBuckets 方法,如果你看不懂上面图中蓝色格子数字是什么意思,那就认真看看这个方法: 看看上面的 transferTimeoutsToBuckets 方法,如果你看不懂上面图中蓝色格子数字是什么意思,那就认真看看这个方法:
`io.netty.util.HashedWheelTimer.Worker#transferTimeoutsToBuckets` `io.netty.util.HashedWheelTimer.Worker#transferTimeoutsToBuckets`
```java ```java
private void transferTimeoutsToBuckets() { private void transferTimeoutsToBuckets() {
// transfer only max. 100000 timeouts per tick to prevent a thread to stale the workerThread when it just // transfer only max. 100000 timeouts per tick to prevent a thread to stale the workerThread when it just
@ -309,6 +317,7 @@
继续看看上面 run 方法中的 bucket.expireTimeouts(deadline);,这里面就是拿出任务并执行的逻辑: 继续看看上面 run 方法中的 bucket.expireTimeouts(deadline);,这里面就是拿出任务并执行的逻辑:
`io.netty.util.HashedWheelTimer.HashedWheelBucket#expireTimeouts` `io.netty.util.HashedWheelTimer.HashedWheelBucket#expireTimeouts`
```java ```java
/** /**
* Expire all {@link HashedWheelTimeout}s for the given {@code deadline}. * Expire all {@link HashedWheelTimeout}s for the given {@code deadline}.
@ -356,6 +365,7 @@ schedule方法也是Netty的定时任务实现之一但是底层的数据结
首先来到如下代码: 首先来到如下代码:
`io.netty.util.concurrent.AbstractScheduledEventExecutor#schedule(java.lang.Runnable, long, java.util.concurrent.TimeUnit)` `io.netty.util.concurrent.AbstractScheduledEventExecutor#schedule(java.lang.Runnable, long, java.util.concurrent.TimeUnit)`
```java ```java
@Override @Override
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) { public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
@ -373,6 +383,7 @@ schedule方法也是Netty的定时任务实现之一但是底层的数据结
继续跟进 schedule 方法看看: 继续跟进 schedule 方法看看:
`io.netty.util.concurrent.AbstractScheduledEventExecutor#schedule(io.netty.util.concurrent.ScheduledFutureTask<V>)` `io.netty.util.concurrent.AbstractScheduledEventExecutor#schedule(io.netty.util.concurrent.ScheduledFutureTask<V>)`
```java ```java
private <V> ScheduledFuture<V> schedule(final ScheduledFutureTask<V> task) { private <V> ScheduledFuture<V> schedule(final ScheduledFutureTask<V> task) {
if (inEventLoop()) { if (inEventLoop()) {
@ -392,6 +403,7 @@ schedule方法也是Netty的定时任务实现之一但是底层的数据结
继续跟进 scheduledTaskQueue()方法: 继续跟进 scheduledTaskQueue()方法:
`io.netty.util.concurrent.AbstractScheduledEventExecutor#scheduledTaskQueue` `io.netty.util.concurrent.AbstractScheduledEventExecutor#scheduledTaskQueue`
```java ```java
PriorityQueue<ScheduledFutureTask<?>> scheduledTaskQueue() { PriorityQueue<ScheduledFutureTask<?>> scheduledTaskQueue() {
if (scheduledTaskQueue == null) { if (scheduledTaskQueue == null) {
@ -413,29 +425,33 @@ schedule方法也是Netty的定时任务实现之一但是底层的数据结
这里我就直接贴下网上大佬给出的解释: 这里我就直接贴下网上大佬给出的解释:
如果使用最小堆实现的优先级队列: 如果使用最小堆实现的优先级队列:
![最小堆.png](https://github.com/mimajiushi/img/blob/master/netty/image_1595756711656.png) ![最小堆.png](../../../images/Netty/image_1595756711656.png)
- 大致意思就是你的任务如果插入到堆顶,时间复杂度为 O(log(n))。 - 大致意思就是你的任务如果插入到堆顶,时间复杂度为 O(log(n))。
如果使用链表(既然有说道,那就扩展下): 如果使用链表(既然有说道,那就扩展下):
![链表.png](https://www.wenjie.store/blog/img/image_1595756928493.png) ![链表.png](../../../images/Netty/image_1595756928493.png)
- 中间插入后的事件复杂度为 O(n) - 中间插入后的事件复杂度为 O(n)
单个时间轮: 单个时间轮:
![单个时间轮.png](https://www.wenjie.store/blog/img/image_1595757035360.png) ![单个时间轮.png](../../../images/Netty/image_1595757035360.png)
- 复杂度可以降至 O(1)。 - 复杂度可以降至 O(1)。
记录轮数的时间轮(其实就是文章开头的那个): 记录轮数的时间轮(其实就是文章开头的那个):
![记录轮数的时间轮.png](https://www.wenjie.store/blog/img/image_1595757110003.png) ![记录轮数的时间轮.png](../../../images/Netty/image_1595757110003.png)
层级时间轮: 层级时间轮:
![层级时间轮.png](https://www.wenjie.store/blog/img/image_1595757328715.png) ![层级时间轮.png](../../../images/Netty/image_1595757328715.png)
- 时间复杂度是 O(n)n 是轮子的数量,除此之外还要计算一个轮子上的 bucket。 - 时间复杂度是 O(n)n 是轮子的数量,除此之外还要计算一个轮子上的 bucket。
### 单时间轮缺点 ### 单时间轮缺点
根据上面的图其实不难理解,如果任务是很久之后才执行的、同时要保证任务低延迟,那么单个时间轮所需的 bucket 数就会变得非常多从而导致内存占用持续升高CPU 空转时间还是不变的,仅仅是内存需求变高了),如下图: 根据上面的图其实不难理解,如果任务是很久之后才执行的、同时要保证任务低延迟,那么单个时间轮所需的 bucket 数就会变得非常多从而导致内存占用持续升高CPU 空转时间还是不变的,仅仅是内存需求变高了),如下图:
![image.png](https://www.wenjie.store/blog/img/image_1595758329809.png) ![image.png](../../../images/Netty/image_1595758329809.png)
Netty 对于单个时间轮的优化方式就是记录下 remainingRounds从而减少 bucket 过多的内存占用。 Netty 对于单个时间轮的优化方式就是记录下 remainingRounds从而减少 bucket 过多的内存占用。
@ -449,11 +465,13 @@ Netty对于单个时间轮的优化方式就是记录下remainingRounds从而
- A而 ScheduledExecutorService 不会有这个问题。 - A而 ScheduledExecutorService 不会有这个问题。
另外Netty 时间轮的实现模型抽象出来是大概这个样子的: 另外Netty 时间轮的实现模型抽象出来是大概这个样子的:
```java ```java
for(Tasks task : tasks) { for(Tasks task : tasks) {
task.doXxx(); task.doXxx();
} }
``` ```
这个抽象是个什么意思呢?你要注意一个点,这里的任务循环执行是同步的,**这意味着你第一个任务执行很慢延迟很高,那么后面的任务全都会被堵住**,所以你加进时间轮的任务不可以是耗时任务,比如一些延迟很高的数据库查询,如果有这种耗时任务,最好再嵌入线程池处理,不要让任务阻塞在这一层。 这个抽象是个什么意思呢?你要注意一个点,这里的任务循环执行是同步的,**这意味着你第一个任务执行很慢延迟很高,那么后面的任务全都会被堵住**,所以你加进时间轮的任务不可以是耗时任务,比如一些延迟很高的数据库查询,如果有这种耗时任务,最好再嵌入线程池处理,不要让任务阻塞在这一层。
> 原文链接https://wenjie.store/archives/netty-hashedwheeltimer-and-schedule > 原文链接https://wenjie.store/archives/netty-hashedwheeltimer-and-schedule

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB