netty源码解析三之Reactor运转架构

三.Reactor运转架构

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Reactor线程其实执行的就是一个死循环,在死循环中不断的通过Selector去轮训IO就绪事件,如果发生IO就绪事件则从Selector系统调用中返回并处理IO就绪事件,如果没有发生IO就绪事件则一直阻塞Selector系统调用上,直到满足Selector唤醒条件

以下三个条件中只要满足任意一个条件,Reactor线程就会被从Selector上唤醒:

  • 当Selector轮询到有IO活跃事件发生时。
  • 当Reactor线程需要执行的定时任务到达任务执行时间deadline时。
  • 当有异步任务提交给Reactor时,Reactor线程需要从Selector上被唤醒,这样才能及时的去执行异步任务

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Reactor线程启动流程

 /**
     * 正常情况下Reactor线程从Selector中被唤醒有两种情况:
     *  轮询到有IO就绪事件发生。
     *  有异步任务或者定时任务需要执行。
     * 而JDK epoll 空轮询 BUG会在上述两种情况都没有发生的时候,Reactor线程会意外的从Selector中被唤醒,导致CPU空转。
     */
    @Override
    protected void run() {
        //记录轮询次数 用于解决JDK epoll的空轮训bug
        int selectCnt = 0;
        for (;;) {
            try {
                //轮询结果
                int strategy;
                try {
                    //根据轮询策略获取轮询结果 这里的hasTasks()主要检查的是普通队列和尾部队列中是否有异步任务等待执行
                    //-1:表示没有异步任务执行,Reactor线程可以安心的阻塞在Selector上等待IO就绪事件发生。
                    //0:表示有异步任务执行,但是没有io就绪事件执行。走default直接进入了处理异步任务的逻辑部分。
                    //>0:即有异步任务也有就绪事件执行。流程通过default分支直接进入了处理IO就绪事件和执行异步任务逻辑部分。
                    strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());
                    switch (strategy) {
                    case SelectStrategy.CONTINUE:
                        continue;

                    case SelectStrategy.BUSY_WAIT:
                        // fall-through to SELECT since the busy-wait is not supported with NIO
                    //由于 NIO 不支持自旋,因此直接选择 SELECT
                    case SelectStrategy.SELECT:
                        //当前没有异步任务执行,Reactor线程可以放心的阻塞等待IO就绪事件
                        //从定时任务队列中取出即将快要执行的定时任务deadline
                        long curDeadlineNanos = nextScheduledTaskDeadlineNanos();
                        if (curDeadlineNanos == -1L) {
                            // -1代表当前定时任务队列中没有定时任务
                            curDeadlineNanos = NONE; // nothing on the calendar
                        }
                        //最早执行定时任务的deadline作为 select的阻塞时间,意思是到了定时任务的执行时间
                        //不管有无IO就绪事件,必须唤醒selector,从而使reactor线程执行定时任务
                        nextWakeupNanos.set(curDeadlineNanos);
                        try {
                            //再次检查普通任务队列中是否有异步任务
                            if (!hasTasks()) {
                                //没有的话开始select阻塞轮询IO就绪事件
                                //假设该定时任务等待时间过长,也没事。执行异步任务时会唤醒。
                                strategy = select(curDeadlineNanos);
                            }
                        } finally {
                            // This update is just to help block unnecessary selector wakeups
                            // so use of lazySet is ok (no race condition)
                            // 执行到这里说明Reactor已经从Selector上被唤醒了
                            // 设置Reactor的状态为苏醒状态AWAKE
                            // lazySet优化不必要的volatile操作,不使用内存屏障,不保证写操作的可见性(单线程不需要保证)
                            nextWakeupNanos.lazySet(AWAKE);
                        }
                        // fall through
                    default:
                    }
                } catch (IOException e) {
                    // If we receive an IOException here its because the Selector is messed up. Let's rebuild
                    // the selector and retry. https://github.com/netty/netty/issues/8566
                    rebuildSelector0();
                    selectCnt = 0;
                    handleLoopException(e);
                    continue;
                }
//                执行到这里说明满足了唤醒条件,Reactor线程从selector上被唤醒开始处理IO就绪事件和执行异步任务
                /**
                 * Reactor线程需要保证及时的执行异步任务,只要有异步任务提交,就需要退出轮询。
                 * 有IO事件就优先处理IO事件,然后处理异步任务
                 * */
                selectCnt++;
                cancelledKeys = 0;
                //主要用于从IO就绪的SelectedKeys集合中剔除已经失效的selectKey
                needsToSelectAgain = false;
                /*
                设置在事件循环中为 IO 花费的所需时间量的百分比。值范围为 1-100。默认值为50.
                这意味着事件循环将尝试为 IO 花费与非 IO 任务相同的时间。数字越小,花在非 IO 任务上的时间就越多。
                如果值设置为 100,此功能将被禁用,事件循环将不会尝试平衡 IO 和非 IO 任务。
                 */
                final int ioRatio = this.ioRatio;
                boolean ranTasks;
                if (ioRatio == 100) {
                    try {
                        //执行所有IO就绪事件
                        if (strategy > 0) {//表示有连接连上
                            processSelectedKeys();
                        }
                    } finally {
                        // Ensure we always run tasks.
                        //紧接着执行所有异步任务
                        ranTasks = runAllTasks();
                    }
                } else if (strategy > 0) {
                    final long ioStartTime = System.nanoTime();
                    try {
                        //先执行所有io任务
                        processSelectedKeys();
                    } finally {
                        // Ensure we always run tasks.
                        final long ioTime = System.nanoTime() - ioStartTime;
                        //然后执行异步任务
                        //执行时间为:ioTime * (100 - ioRatio) / ioRatio
                        ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                    }
                } else {
                    //运行所有任务,最多只执行64个,防止Reactor线程由于处理异步任务时间过长而导致I/O 事件得不到及时地处理。
                    ranTasks = runAllTasks(0); // This will run the minimum number of tasks
                }
                //判断是否触发JDK Epoll BUG 触发空轮询
                //ranTasks:表示是否执行过一次异步任务
                //strategy:JDK NIO Selector的select方法的返回值,用来表示IO就绪的Channel个数。
                if (ranTasks || strategy > 0) {
                    if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS && logger.isDebugEnabled()) {
                        logger.debug("Selector.select() returned prematurely {} times in a row for Selector {}.",
                                selectCnt - 1, selector);
                    }
                    selectCnt = 0;
                } else if (unexpectedSelectorWakeup(selectCnt)) { // Unexpected wakeup (unusual case)
                //没有执行任何异步任务和IO就绪的事件,说明被意外唤醒。
                    //既没有IO就绪事件,也没有异步任务,Reactor线程从Selector上被异常唤醒 触发JDK Epoll空轮训BUG
                    //重新构建Selector,selectCnt归零
                    selectCnt = 0;
                }
            } catch (CancelledKeyException e) {
                // Harmless exception - log anyway
                if (logger.isDebugEnabled()) {
                    logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector {} - JDK bug?",
                            selector, e);
                }
            } catch (Error e) {
                throw (Error) e;
            } catch (Throwable t) {
                handleLoopException(t);
            } finally {
                // Always handle shutdown even if the loop processing threw an exception.
                try {
                    if (isShuttingDown()) {
                        closeAll();
                        if (confirmShutdown()) {
                            return;
                        }
                    }
                } catch (Error e) {
                    throw (Error) e;
                } catch (Throwable t) {
                    handleLoopException(t);
                }
            }
        }
    }

Reactor主要做了如下事情:

  • 通过jdk nio selector轮训注册在reactor上的所有channel感兴趣的io事件,对NioServerSocketChannel来说主要负责OP_ACCEPT事件。对于客户端NioSocketChannel来说因为它主要负责处理连接上的读写事件所以监听的是OP_READOP_WRITE事件。netty只会自动注册OP_READ事件,而OP_WRITE事件是在当Socket写入缓冲区以满无法继续写入发送数据时由用户自己注册。
  • 如果有异步任务需要执行,则立马停止轮训,转而去执行异步任务。
    • 即有io就绪,也有异步任务。优先处理io就绪,然后根据ioRatio设置的执行时间比例决定执行多长时间异步任务。
    • 只有异步任务或者定时任务,执行64个任务,防止耽误轮训io事件执行。
  • 触发epoll空轮训则重建selector。

JDK epoll 空轮询 BUG:https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6670302

1. Reactor线程轮询IO就绪事件

首先轮询IO就绪事件,SelectStrategyFactory用于指定轮询策略的,默认实现为DefaultSelectStrategyFactory.INSTANCE

public class NioEventLoopGroup extends MultithreadEventLoopGroup {
public NioEventLoopGroup(
        int nThreads, Executor executor, final SelectorProvider selectorProvider) {
    this(nThreads, executor, selectorProvider, DefaultSelectStrategyFactory.INSTANCE);
}
}

public final class DefaultSelectStrategyFactory implements SelectStrategyFactory {
    public static final SelectStrategyFactory INSTANCE = new DefaultSelectStrategyFactory();
}

public class NioEventLoopGroup extends MultithreadEventLoopGroup {
@Override
protected EventLoop newChild(Executor executor, Object... args) throws Exception {
    //存储Reactor待执行的异步任务
    EventLoopTaskQueueFactory queueFactory = args.length == 4 ? (EventLoopTaskQueueFactory) args[3] : null;
    return new NioEventLoop(this, executor, (SelectorProvider) args[0],
        ((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2], queueFactory);
}
}

public final class DefaultSelectStrategyFactory implements SelectStrategyFactory {
      @Override
    public SelectStrategy newSelectStrategy() {
        return DefaultSelectStrategy.INSTANCE;
    }
}

SelectStrategy指定轮训策略,这里默认实现为:DefaultSelectStrategy

Reactor线程开启轮询的一开始,就是用这个selectStrategy去计算一个轮询策略strategy,后续会根据这个strategy进行不同的逻辑处理。

 protected void run() {
        for (;;) {
            try {
                //轮询结果
                int strategy;
                try {
                    //根据轮询策略获取轮询结果 这里的hasTasks()主要检查的是普通队列和尾部队列中是否有异步任务等待执行
                    strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());
                    switch (strategy) {
                        case SelectStrategy.CONTINUE:
                        //重新开启一轮IO轮询
                        continue;
                    case SelectStrategy.BUSY_WAIT:
                        //Reactor线程进行自旋轮询,由于NIO 不支持自旋操作,所以这里直接跳到SelectStrategy.SELECT策略。
                        // fall-through to SELECT since the busy-wait is not supported with NIO
                    case SelectStrategy.SELECT:
                        //当前没有异步任务执行,Reactor线程可以放心的阻塞等待IO就绪事件
                    default:
                    }
                } catch (IOException e) {
                    continue;
                }

            } catch (Exception e) {
            } catch (Error e) {
            } finally {
            }
        }
    }

1.1 轮询策略

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

首先查看三种轮训策略。

public interface SelectStrategy {
    //此时没有任何异步任务需要执行,Reactor线程可以安心的阻塞在Selector上等待IO就绪事件的来临。
    int SELECT = -1;
    //重新开启一轮IO轮询
    int CONTINUE = -2;
    // Reactor线程进行自旋轮询,由于NIO 不支持自旋操作,所以这里直接跳到SelectStrategy.SELECT策略。
    int BUSY_WAIT = -3;
}

下面我们来看下轮询策略的计算逻辑calculateStrategy

public abstract class SingleThreadEventLoop extends SingleThreadEventExecutor implements EventLoop {
  
      @Override
    protected boolean hasTasks() {
        //查看Reactor中的异步任务队列taskQueue和用于统计信息任务用的尾部队列tailTask是否有异步任务。
        return super.hasTasks() || !tailTasks.isEmpty();
    }
}

public abstract class SingleThreadEventExecutor extends AbstractScheduledEventExecutor implements OrderedEventExecutor {
      protected boolean hasTasks() {
        assert inEventLoop();
        return !taskQueue.isEmpty();
    }
}
  • Reactor线程的轮询工作开始之前,需要首先判断下当前是否有异步任务需要执行。判断依据就是查看Reactor中的异步任务队列taskQueue和用于统计信息任务用的尾部队列tailTask是否有异步任务
final class DefaultSelectStrategy implements SelectStrategy {
    @Override
    public int calculateStrategy(IntSupplier selectSupplier, boolean hasTasks) throws Exception {
        /**
         * Reactor线程要保证及时的执行异步任务
         * 1:如果有异步任务等待执行,则马上执行selectNow()非阻塞轮询一次IO就绪事件
         * 2:没有异步任务,则跳到switch select分支
         * */
        return hasTasks ? selectSupplier.get() : SelectStrategy.SELECT;
    }
    }
  • 如果Reactor中有异步任务需要执行,那么Reactor线程需要立即执行,不能阻塞在Selector上。在返回前需要再顺带调用selectNow()非阻塞查看一下当前是否有IO就绪事件发生。如果有,那么正好可以和异步任务一起被处理,如果没有,则及时地处理异步任务
private final IntSupplier selectNowSupplier = new IntSupplier() {
    @Override
    public int get() throws Exception {
        return selectNow();
    }
};

    int selectNow() throws IOException {
        //非阻塞,返回的数值表示有多少个IO就绪的Channel。
        return selector.selectNow();
    }

如果当前Reactor线程没有异步任务需要执行,那么calculateStrategy方法直接返回SelectStrategy.SELECT也就是SelectStrategy接口中定义的常量-1。当calculateStrategy方法通过selectNow()返回非零数值时,表示此时有IO就绪Channel,返回的数值表示有多少个IO就绪Channel

这里strategy有三种 情况

  1. 返回 = -1:表示没有异步任务,switch逻辑分支进入SelectStrategy.SELECT分支
  2. 返回 = 0:有任务没有io就绪任务,switch逻辑分支进入default分支
  3. 返回 > 0:有任务且有io就绪任务,switch逻辑分支进入default分支

首先查看SelectStrategy.SELECT分支策略

1.2 轮询逻辑

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

case SelectStrategy.SELECT:
    //当前没有异步任务执行,Reactor线程可以放心的阻塞等待IO就绪事件
    //从定时任务队列中取出即将快要执行的定时任务deadline
    long curDeadlineNanos = nextScheduledTaskDeadlineNanos();
    if (curDeadlineNanos == -1L) {
        // -1代表当前定时任务队列中没有定时任务
        curDeadlineNanos = NONE; // nothing on the calendar
    }
    //最早执行定时任务的deadline作为 select的阻塞时间,意思是到了定时任务的执行时间
    //不管有无IO就绪事件,必须唤醒selector,从而使reactor线程执行定时任务
    nextWakeupNanos.set(curDeadlineNanos);
    try {
        //再次检查普通任务队列中是否有异步任务
        if (!hasTasks()) {
            //没有的话开始select阻塞轮询IO就绪事件
            //假设该定时任务等待时间过长,也没事。执行异步任务时会唤醒。
            strategy = select(curDeadlineNanos);
        }
    } finally {
        // This update is just to help block unnecessary selector wakeups
        // so use of lazySet is ok (no race condition)
        // 执行到这里说明Reactor已经从Selector上被唤醒了
        // 设置Reactor的状态为苏醒状态AWAKE
        // lazySet优化不必要的volatile操作,不使用内存屏障,不保证写操作的可见性(单线程不需要保证)
        nextWakeupNanos.lazySet(AWAKE);
    }
    // fall through

此时Reactor会阻塞获取io任务,那么阻塞多久是怎么计算的了?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Reactor线程除了要轮询Channel上的IO就绪事件,以及处理IO就绪事件外,还有一个任务就是负责执行Netty框架中的异步任务。而Netty框架中的异步任务分为三类:

  • 存放在普通任务队列taskQueue中的普通异步任务。
  • 存放在尾部队列tailTasks中的用于执行统计任务等收尾动作的尾部任务。
  • 还有一种就是这里即将提到的定时任务。存放在Reactor中的定时任务队列scheduledTaskQueue中。

从ReactorNioEventLoop类中的继承结构我们也可以看出,Reactor具备执行定时任务的能力。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

阻塞多久取决于定时任务最近一次任务的执行时间。

1.2.1 Reactor的轮询超时时间

public abstract class AbstractScheduledEventExecutor extends AbstractEventExecutor {
protected final long nextScheduledTaskDeadlineNanos() {
    //查看定时任务头部任务,也就是最近一次执行任务
    ScheduledFutureTask<?> scheduledTask = peekScheduledTask();
    //返回当前定时任务最近的一个执行时间点
    return scheduledTask != null ? scheduledTask.deadlineNanos() : -1;
}
  
      final ScheduledFutureTask<?> peekScheduledTask() {
        Queue<ScheduledFutureTask<?>> scheduledTaskQueue = this.scheduledTaskQueue;
        return scheduledTaskQueue != null ? scheduledTaskQueue.peek() : null;
    }

}

final class ScheduledFutureTask<V> extends PromiseTask<V> implements ScheduledFuture<V>, PriorityQueueNode {
        //返回当前Reactor定时任务队列中最近的一个定时任务的deadline时间点
    private long deadlineNanos;
      public long deadlineNanos() {
        return deadlineNanos;
    }
  
}

获取最近一次定时任务执行时间后,阻塞时间。当没有定时任务的时候则一直阻塞,这里执行异步任务的时候会被唤醒。

private static final long AWAKE = -1L;
private static final long NONE = Long.MAX_VALUE;
private final AtomicLong nextWakeupNanos = new AtomicLong(AWAKE);
long curDeadlineNanos = nextScheduledTaskDeadlineNanos();
if (curDeadlineNanos == -1L) {
    // -1代表当前定时任务队列中没有定时任务
    curDeadlineNanos = NONE; // nothing on the calendar
}
//最早执行定时任务的deadline作为 select的阻塞时间,意思是到了定时任务的执行时间
//不管有无IO就绪事件,必须唤醒selector,从而使reactor线程执行定时任务
nextWakeupNanos.set(curDeadlineNanos);
try {
    //再次检查普通任务队列中是否有异步任务
    if (!hasTasks()) {
        //没有的话开始select阻塞轮询IO就绪事件
        //假设该定时任务等待时间过长,也没事。执行异步任务时会唤醒。
        strategy = select(curDeadlineNanos);
    }
} finally {
    // This update is just to help block unnecessary selector wakeups
    // so use of lazySet is ok (no race condition)
    // 执行到这里说明Reactor已经从Selector上被唤醒了
    // 设置Reactor的状态为苏醒状态AWAKE
    // lazySet优化不必要的volatile操作,不使用内存屏障,不保证写操作的可见性(单线程不需要保证)
    nextWakeupNanos.lazySet(AWAKE);
}

在阻塞之前还会再次确认是否有普通任务,如果有任务则代码向下继续执行异步任务,停止io就绪事件轮训。

这里会出现一个问题,倘若reactor一直阻塞等待就绪事件到来,此时来了异步任务,且没有io就绪任务到来,这里会怎么处理了?

public abstract class SingleThreadEventExecutor extends AbstractScheduledEventExecutor implements OrderedEventExecutor {
private void execute(Runnable task, boolean immediate) {
    //immediate:提交的task需要立即执行
    //addTaskWakesUp:true 表示当且仅当只有调用addTask方法时才会唤醒Reactor线程。调用别的方法并不会唤醒Reactor线程。
                // 在初始化NioEventLoop时会设置为false,表示并不是只有addTask方法才能唤醒Reactor线程 还有其他方法可以唤醒Reactor线程,
                // 比如这里的execute方法就会唤醒Reactor线程。
    if (!addTaskWakesUp && immediate) {
        //4.将Reactor线程从Selector上唤醒
        wakeup(inEventLoop);
    }
}
}

这里分别查看二个参数:

  • addTaskWakesUp:true 表示当且仅当只有调用addTask方法时才会唤醒Reactor线程。调用别的方法并不会唤醒Reactor线程。
    在初始化NioEventLoop时会设置为false,表示并不是只有addTask方法才能唤醒Reactor线程 还有其他方法可以唤醒Reactor线程,比如这里的execute方法就会唤醒Reactor线程。
  • immediate:提交的task需要立即执行
public final class NioEventLoop extends SingleThreadEventLoop {
@Override
protected void wakeup(boolean inEventLoop) {
    //当nextWakeupNanos = AWAKE时表示当前Reactor正处于苏醒状态,既然是苏醒状态也就没有必要去执行selector.wakeup()重复唤醒Reactor了,同时也能省去这一次的系统调用开销。
    if (!inEventLoop && nextWakeupNanos.getAndSet(AWAKE) != AWAKE) {
        //Reactor线程从Selector上唤醒
        selector.wakeup();
    }
}
}

当nextWakeupNanos = AWAKE时表示当前Reactor正处于苏醒状态,既然是苏醒状态也就没有必要去执行selector.wakeup()重复唤醒Reactor了,同时也能省去这一次的系统调用开销。

private int select(long deadlineNanos) throws IOException {
    //为null,则安心阻塞。
    if (deadlineNanos == NONE) {
        return selector.select();
    }
    // Timeout will only be 0 if deadline is within 5 microsecs
    //定时任务<5微秒时,则立即执行selectNow,否则最少阻塞1毫秒
    long timeoutMillis = deadlineToDelayNanos(deadlineNanos + 995000L) / 1000000L;
    //阻塞在Selector上等待IO就绪事件直到最近的一个定时任务执行时间点deadline到达。
    return timeoutMillis <= 0 ? selector.selectNow() : selector.select(timeoutMillis);
}

deadlineNanos不为NONE,表示此时Reactor定时任务需要执行,Reactor线程需要阻塞在Selector上等待IO就绪事件直到最近的一个定时任务执行时间点deadline到达。

这里的deadlineNanos表示的就是Reactor中最近的一个定时任务执行时间点deadline,单位是纳秒。指的是一个绝对时间

而我们需要计算的是Reactor线程阻塞在Selector的超时时间timeoutMillis,单位是毫秒,指的是一个相对时间

Reactor线程开始阻塞在Selector上之前,我们需要将这个单位为纳秒的绝对时间deadlineNanos转化为单位为毫秒的相对时间timeoutMillis

通过deadlineToDelayNanos方法计算timeoutMillis的时候,为什么要给deadlineNanos在加上0.995毫秒呢??

假设最近的一次定时任务在5微秒内到达,转成毫秒的时候会直接变成等待0毫秒。然后立即执行一次selectNow,此时是获取不到任务的,因为还要5微秒后才有任务需要执行,这里在deadlineNanos在加上0.995毫秒凑成1毫秒不能让其为0。也就是说5微秒以外的任务最少等待一毫秒后执行。

protected static long deadlineToDelayNanos(long deadlineNanos) {
    return ScheduledFutureTask.deadlineToDelayNanos(deadlineNanos);
}

    static long deadlineToDelayNanos(long deadlineNanos) {
        //超时时间-(当前时间-启动时间)=最终需要等待时间
        //以当前时间节点为基准,往后推迟一个启动时间执行任务
        return deadlineNanos == 0L ? 0L : Math.max(0L, deadlineNanos - nanoTime());
    }
    
        static long nanoTime() {
        return System.nanoTime() - START_TIME;
    }
    
        //服务启动时间点
    private static final long START_TIME = System.nanoTime();

在创建定时任务时会通过deadlineNanos方法计算定时任务的执行deadlinedeadline的计算逻辑是当前时间点+任务延时delay-系统启动时间这里需要扣除系统启动的时间

所以这里在通过deadline计算延时delay(也就是timeout)的时候需要在加上系统启动的时间 : deadlineNanos - nanoTime()

当通过deadlineToDelayNanos计算出的timeoutMillis <= 0时,表示Reactor目前有临近的定时任务需要执行,这时候就需要立马返回,不能阻塞在Selector上影响定时任务的执行。当然在返回执行定时任务前,需要在顺手通过selector.selectNow()非阻塞轮询一下Channel上是否有IO就绪事件到达,防止耽误IO事件的处理。真是操碎了心~~

timeoutMillis > 0时,Reactor线程就可以安心的阻塞在Selector上等待IO事件的到来,直到timeoutMillis超时时间到达。

return timeoutMillis <= 0 ? selector.selectNow() : selector.select(timeoutMillis);

当注册在Reactor上的Channel中有IO事件到来时,Reactor线程就会从selector.select(timeoutMillis)调用中唤醒,立即去处理IO就绪事件.

当Reactor轮训到io事件或异步任务时,会被唤醒执行,当然不会无限的执行异步任务从而影响io吞吐,这里通过ioRatio变量来调配Reactor线程在处理IO事件和执行异步任务之间的CPU时间分配比例。

2. Reactor处理IO与处理异步任务的时间比例分配

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

private volatile int ioRatio = 50;
 /*
                设置在事件循环中为 IO 花费的所需时间量的百分比。值范围为 1-100。默认值为50.
                这意味着事件循环将尝试为 IO 花费与非 IO 任务相同的时间。数字越小,花在非 IO 任务上的时间就越多。
                如果值设置为 100,此功能将被禁用,事件循环将不会尝试平衡 IO 和非 IO 任务。
                 */
final int ioRatio = this.ioRatio;
boolean ranTasks;
if (ioRatio == 100) {
    try {
        //此时strategy记录到达io事件个数,执行所有IO就绪事件
        if (strategy > 0) {
            //表示有连接连上
            processSelectedKeys();
        }
    } finally {
        // Ensure we always run tasks.
        //紧接着执行所有异步任务
        ranTasks = runAllTasks();
    }
} else if (strategy > 0) {
    final long ioStartTime = System.nanoTime();
    try {
        //先执行所有io任务
        processSelectedKeys();
    } finally {
        // Ensure we always run tasks.
        //计算执行了io事件花费时间
        final long ioTime = System.nanoTime() - ioStartTime;
        //然后执行异步任务,执行时间为:ioTime * (100 - ioRatio) / ioRatio
        ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
    }
} else {
    //运行所有任务,最多只执行64个,防止Reactor线程由于处理异步任务时间过长而导致I/O 事件得不到及时地处理。
    ranTasks = runAllTasks(0); // This will run the minimum number of tasks
}

strategy此时记录到达io事件个数。

  • 这里当ioRatio设置为100,表示禁用评估时间。执行完所有io就绪事件后再执行所有任务。
  • 当有io就绪事件到达时,执行到达事件后,通过执行io花费时间和ioRatio计算异步任务执行时间,接着执行任务。
  • 没有就绪事件到达时,运行任务,一次最多运行64个任务。

3. Reactor线程处理IO就绪事件

    //记录就绪key
private SelectedSelectionKeySet selectedKeys;

private void processSelectedKeys() {
   //是否采用netty优化后的selectedKey集合类型 是由变量DISABLE_KEY_SET_OPTIMIZATION决定的 默认为false
    if (selectedKeys != null) {
        processSelectedKeysOptimized();
    } else {
        processSelectedKeysPlain(selector.selectedKeys());
    }
}

Netty中通过优化开关DISABLE_KEY_SET_OPTIMIZATION控制是否对JDK NIO Selector进行优化。默认是需要优化。

在优化开关开启的情况下,Netty会将创建的SelectedSelectionKeySet 集合保存在NioEventLoopprivate SelectedSelectionKeySet selectedKeys字段中,方便Reactor线程直接从这里获取IO就绪SelectionKey

在优化开关关闭的情况下,Netty会直接采用JDK NIO Selector的默认实现。此时NioEventLoopselectedKeys字段就会为null

3.1 processSelectedKeysPlain

首先通过selectedKeys获取就绪事件。

processSelectedKeysPlain(selector.selectedKeys());
private void processSelectedKeysPlain(Set<SelectionKey> selectedKeys) {
    // check if the set is empty and if so just return to not create garbage by
    // creating a new Iterator every time even if there is nothing to process.
    // See https://github.com/netty/netty/issues/597
    if (selectedKeys.isEmpty()) {
        return;
    }

    Iterator<SelectionKey> i = selectedKeys.iterator();
    for (;;) {
        final SelectionKey k = i.next();
        //获取附件
        final Object a = k.attachment();

        //注意每次迭代末尾的keyIterator.remove()调用。Selector不会自己从已选择键集中移除SelectionKey实例。
        //必须在处理完通道时自己移除。下次该通道变成就绪时,Selector会再次将其放入已选择键集中。
        i.remove();

        if (a instanceof AbstractNioChannel) {
            processSelectedKey(k, (AbstractNioChannel) a);
        } else {
        //Netty提供给用户可以自定义一些当Channel上发生IO就绪事件时的自定义处理。
            @SuppressWarnings("unchecked")
            NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
            processSelectedKey(k, task);
        }

        if (!i.hasNext()) {
            break;
        }

        //目的是再次进入for循环 移除失效的selectKey(socketChannel可能从selector上移除)
        if (needsToSelectAgain) {
            //当在本次轮询期间,假如大量的Channel从Selector中取消,Selector中的就绪集合selectedKeys中依然会保存这些Channel对应SelectionKey直到下次轮询。
            // 那么当然会影响本次轮询结果selectedKeys的有效性。

            //所以为了保证Selector中所有KeySet的有效性,需要在Channel取消个数达到256时,触发一次selectNow,目的是清除无效的SelectionKey。
            selectAgain();
            selectedKeys = selector.selectedKeys();

            // Create the iterator again to avoid ConcurrentModificationException
            if (selectedKeys.isEmpty()) {
                break;
            } else {
                i = selectedKeys.iterator();
            }
        }
    }
}

这里获取keys的迭代器,然后遍历处理每个到达的就绪事件。

首先获取附件,该附件在注册到选择器的时候将channel对象作为附件传递的。

public abstract class AbstractNioChannel extends AbstractChannel { 
volatile SelectionKey selectionKey;
@Override
protected void doRegister() throws Exception {
    boolean selected = false;
    for (;;) {
        try {
            //注册选择器,并将chanle封装到附件,并将selectionKey保存channel中
            selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
            return;
        } catch (CancelledKeyException e) {
        ..........
        }
    }
}
}

对于客户端连接事件(OP_ACCEPT)活跃时,这里的Channel类型NioServerSocketChannel。对于客户端读写事件(ReadWrite)活跃时,这里的Channel类型NioSocketChannel

3.1.2 处理Channel上的IO事件

if (a instanceof AbstractNioChannel) {
    processSelectedKey(k, (AbstractNioChannel) a);
} else {
//Netty提供给用户可以自定义一些当Channel上发生IO就绪事件时的自定义处理。
    @SuppressWarnings("unchecked")
    NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
    processSelectedKey(k, task);
}

从这里我们可以看出Netty向SelectionKey中的attachment属性附加的对象分为两种:

  • 一种是我们熟悉的Channel,无论是服务端使用的NioServerSocketChannel还是客户端使用的NioSocketChannel都属于AbstractNioChannelChannel上的IO事件是由Netty框架负责处理,也是本小节我们要重点介绍的
  • 另一种就是NioTask,这种类型是Netty提供给用户可以自定义一些当Channel上发生IO就绪事件时的自定义处理。
public interface NioTask<C extends SelectableChannel> {

    void channelReady(C ch, SelectionKey key) throws Exception;


    void channelUnregistered(C ch, Throwable cause) throws Exception;
}
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
    //获取Channel的底层操作类Unsafe
    final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
    //如果SelectionKey已经失效则关闭对应的Channel
    if (!k.isValid()) {
        final EventLoop eventLoop;
        try {
            eventLoop = ch.eventLoop();
        } catch (Throwable ignored) {
            return;
        }
        if (eventLoop == this) {
            unsafe.close(unsafe.voidPromise());
        }
        return;
    }

    try {
        //获取IO就绪事件
        int readyOps = k.readyOps();
        //处理Connect事件
        if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
            int ops = k.interestOps();
            //移除对Connect事件的监听,否则Selector会一直通知
            ops &= ~SelectionKey.OP_CONNECT;
            k.interestOps(ops);
            //触发channelActive事件处理Connect事件
            unsafe.finishConnect();
        }
        //处理Write事件
        if ((readyOps & SelectionKey.OP_WRITE) != 0) {
            ch.unsafe().forceFlush();
        }

        //执行accept或read事件
        if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
            unsafe.read();
        }
    } catch (CancelledKeyException ignored) {
        unsafe.close(unsafe.voidPromise());
    }
}
  • 首先获取unsafe,用于对具体IO就绪事件的处理。

  • 获取IO就绪事件

    • SelectonKey中关于IO事件的集合有两个。一个是interestOps,用于记录Channel感兴趣的IO事件,在ChannelSelector注册完毕后,通过pipeline中的HeadContext节点的ChannelActive事件回调中添加。下面这段代码就是在ChannelActive事件回调中Channel在向Selector注册自己感兴趣的IO事件。

    • public abstract class AbstractNioChannel extends AbstractChannel {
      @Override
      protected void doBeginRead() throws Exception {
          // Channel.read() or ChannelHandlerContext.read() was called
          final SelectionKey selectionKey = this.selectionKey;
          //查看SelectionKey是否有效
          if (!selectionKey.isValid()) {
              return;
          }
      
          readPending = true;
      
          final int interestOps = selectionKey.interestOps();
          if ((interestOps & readInterestOp) == 0) {
              //注册accept事件
              selectionKey.interestOps(interestOps | readInterestOp);
          }
      }
      }
      
    • 另一个就是这里的readyOps,用于记录在Channel感兴趣的IO事件中具体哪些IO事件就绪了。

int readyOps = k.readyOps();

Netty中将各种事件的集合用一个int型变量来保存。

  • &操作判断,某个事件是否在事件集合中:(readyOps & SelectionKey.OP_CONNECT) != 0,这里就是判断Channel是否对Connect事件感兴趣。
  • |操作向事件集合中添加事件:interestOps | readInterestOp
  • 从事件集合中删除某个事件,是通过先将要删除事件取反~,然后在和事件集合做&操作:ops &= ~SelectionKey.OP_CONNECT

3.1.2.1 处理Connect事件

Netty客户端向服务端发起连接,并向客户端的Reactor注册Connect事件,当连接建立成功后,客户端的NioSocketChannel就会产生Connect就绪事件,通过前面内容我们讲的Reactor的运行框架,最终流程会走到这里。

//处理Connect事件
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
    // remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking
    // See https://github.com/netty/netty/issues/924
    int ops = k.interestOps();
    //移除对Connect事件的监听,否则Selector会一直通知
    ops &= ~SelectionKey.OP_CONNECT;
    k.interestOps(ops);
    //触发channelActive事件处理Connect事件
    unsafe.finishConnect();
}

如果IO就绪的事件是Connect事件,那么就调用对应客户端NioSocketChannel中的Unsafe操作类中的finishConnect方法处理Connect事件。这时会在Netty客户端NioSocketChannel中的pipeline中传播ChannelActive事件

最后需要将OP_CONNECT事件从客户端NioSocketChannel所关心的事件集合interestOps中删除。否则Selector会一直通知Connect事件就绪

3.1.2.2 处理Write事件

//处理Write事件
//这里大家只需要记住,OP_WRITE事件的注册是由用户来完成的,当Socket发送缓冲区已满无法继续写入数据时,
// 用户会向Reactor注册OP_WRITE事件,等到Socket发送缓冲区变得可写时,Reactor会收到OP_WRITE事件活跃通知,
// 随后在这里调用客户端NioSocketChannel中的forceFlush方法将剩余数据发送出去。
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
    // Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write
    ch.unsafe().forceFlush();
}

OP_WRITE事件的注册是由用户来完成的,当Socket发送缓冲区已满无法继续写入数据时,用户会向Reactor注册OP_WRITE事件,等到Socket发送缓冲区变得可写时,Reactor会收到OP_WRITE事件活跃通知,随后在这里调用客户端NioSocketChannel中的forceFlush方法将剩余数据发送出去。

3.1.2.3 处理Read事件或者Accept事件

// 执行accept或read事件
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
    unsafe.read();
}

可以看出Netty中处理Read事件Accept事件都是由对应Channel中的Unsafe操作类中的read方法处理。

服务端NioServerSocketChannel中的Read方法处理的是Accept事件,客户端``中的Read方法处理的是Read事件

当客户端连接完成三次握手之后,main reactor中的selector产生OP_ACCEPT事件活跃,main reactor随即被唤醒,来到了OP_ACCEPT事件的处理入口函数开始接收客户端连接。

Netty将OP_ACCEPT事件处理的入口函数封装在NioServerSocketChannel里的底层操作类Unsafe的read方法中。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

而NioServerSocketChannel中的Unsafe操作类实现类型为NioMessageUnsafe定义在上图继承结构中的AbstractNioMessageChannel父类中

3.1.3 从Selector中移除失效的SelectionKey

//用于及时从selectedKeys中清除失效的selectKey 比如 socketChannel从selector上被用户移除
private boolean needsToSelectAgain;
//目的是再次进入for循环 移除失效的selectKey(socketChannel可能从selector上移除)
if (needsToSelectAgain) {
    //当在本次轮询期间,假如大量的Channel从Selector中取消,Selector中的就绪集合selectedKeys中依然会保存这些Channel对应SelectionKey直到下次轮询。
    // 那么当然会影响本次轮询结果selectedKeys的有效性。

    //所以为了保证Selector中所有KeySet的有效性,需要在Channel取消个数达到256时,触发一次selectNow,目的是清除无效的SelectionKey。
    selectAgain();
    selectedKeys = selector.selectedKeys();

    // Create the iterator again to avoid ConcurrentModificationException
    if (selectedKeys.isEmpty()) {
        break;
    } else {
        i = selectedKeys.iterator();
    }
}

可以看到每次执行就绪事件或者异步任务的时候会设置needsToSelectAgain为false。那什么时候为true了?

    protected void run() {
        int selectCnt = 0;
        for (;;) {
            try {
                //轮询结果
                int strategy;
                try {
                    strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());
                    switch (strategy) {
                    case SelectStrategy.CONTINUE:
                        continue;
                    case SelectStrategy.BUSY_WAIT:
                    case SelectStrategy.SELECT:
                    default:
                    }
                } catch (IOException e) {
 
                }

                //主要用于从IO就绪的SelectedKeys集合中剔除已经失效的selectKey
                needsToSelectAgain = false;
                final int ioRatio = this.ioRatio;
                boolean ranTasks;
               
                } else if (strategy > 0) {
                  
                } else {
                   
                }
               
            } catch (CancelledKeyException e) {
               
            }  finally {
                
            }
        }
    }

首先查看Channel的取消注册。

public abstract class AbstractNioChannel extends AbstractChannel {
      //channel注册到Selector后获得的SelectKey
    volatile SelectionKey selectionKey;
  
//取消注册
@Override
protected void doDeregister() throws Exception {
    eventLoop().cancel(selectionKey());
}

    protected SelectionKey selectionKey() {
        assert selectionKey != null;
        return selectionKey;
    }
  
}
public final class NioEventLoop extends SingleThreadEventLoop {
      //记录取消channel注册到selector次数
    private int cancelledKeys;
     private static final int CLEANUP_INTERVAL = 256; 
  
void cancel(SelectionKey key) {
    key.cancel();
    cancelledKeys ++;
    //当取消的socketChannel数量达到256时,设置needsToSelectAgain = true
    // 在io.netty.channel.nio.NioEventLoop.processSelectedKeysPlain 中重新做一次轮询,将失效的selectKey移除,
    // 以保证selectKeySet的有效性
    if (cancelledKeys >= CLEANUP_INTERVAL) {
        cancelledKeys = 0;
        needsToSelectAgain = true;
    }
}
}
  • 这里就是调用JDK NIO SelectionKey的API cancel方法,将ChannelSelector中取消掉。该方法调用后,SelectionKey#isValid将会返回false。并将要取消的这个SelectionKey加入到Selector中的cancelledKeys集合
public final void cancel() {
    // Synchronizing "this" to prevent this key from getting canceled
    // multiple times by different threads, which might cause race
    // condition between selector's select() and channel's close().
    synchronized (this) {
        if (valid) {
            valid = false;
            ((AbstractSelector)selector()).cancel(this);
        }
    }
}
void cancel(SelectionKey k) {                       // package-private
    synchronized (cancelledKeys) {
        cancelledKeys.add(k);
    }
}
  • 接着累加cancelledKeys取消的次数,当达到256后,重建cancelledKeys为0,设置needsToSelectAgain为true。
  • 随后在Selector的**下一次轮询过程中,会将cancelledKeys集合中的SelectionKeySelector所有的KeySet中移除**。这里的KeySet包括Selector用于存放就绪SelectionKeyselectedKeys集合,以及用于存放所有注册的Channel对应的SelectionKeykeys集合
public abstract class SelectorImpl
    extends AbstractSelector
{
    // The set of keys with data ready for an operation
    protected Set<SelectionKey> selectedKeys;
      protected HashSet<SelectionKey> keys;
}
  • 为了保证Selector中所有KeySet的有效性,需要在Channel取消个数达到256时,触发一次selectNow,目的是清除无效的SelectionKey。
private void selectAgain() {
    needsToSelectAgain = false;
    try {
        selector.selectNow();
    } catch (Throwable t) {
        logger.warn("Failed to update SelectionKeys.", t);
    }
}

3.2 processSelectedKeysOptimized

private void processSelectedKeysOptimized() {
    // 在openSelector的时候将JDK中selector实现类中得selectedKeys和publicSelectKeys字段类型
    // 由原来的HashSet类型替换为 Netty优化后的数组实现的SelectedSelectionKeySet类型
    for (int i = 0; i < selectedKeys.size; ++i) {
        final SelectionKey k = selectedKeys.keys[i];
        // null out entry in the array to allow to have it GC'ed once the Channel close
        // See https://github.com/netty/netty/issues/2363

        // 对应迭代器中得remove   selector不会自己清除selectedKey
        selectedKeys.keys[i] = null;

        final Object a = k.attachment();//附件是channel

        if (a instanceof AbstractNioChannel) {
            processSelectedKey(k, (AbstractNioChannel) a);
        } else {
            @SuppressWarnings("unchecked")
            NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
            processSelectedKey(k, task);
        }

        //在最后清除无效的SelectionKey时,在processSelectedKeysPlain中由于采用的是JDK NIO 原生的Selector,
        //  所以只需要执行SelectAgain就可以,Selector会自动清除无效Key。但是在processSelectedKeysOptimized中由于是Netty自己实现的优化类型,
        //  所以需要Netty自己将SelectedSelectionKeySet数组中的SelectionKey全部清除,最后在执行SelectAgain。
        if (needsToSelectAgain) {
            // null out entries in the array to allow to have it GC'ed once the Channel close
            // See https://github.com/netty/netty/issues/2363
            selectedKeys.reset(i + 1);

            selectAgain();
            i = -1;
        }
    }
}
  • netty对selectedKeys集合进行了优化,采用数组替代遍历。
  • 当selectionKey处理完毕后,需要我们手动删除。
    • processSelectedKeysPlain中是直接将其从迭代器中删除。
    • processSelectedKeysOptimized中将其在数组中对应的位置置为Null,方便垃圾回收。
  • 在最后清除无效的SelectionKey时,在processSelectedKeysPlain中由于采用的是JDK NIO 原生的Selector,所以只需要执行SelectAgain就可以,Selector会自动清除无效Key。但是在processSelectedKeysOptimized中由于是Netty自己实现的优化类型,所以需要Netty自己将SelectedSelectionKeySet数组中的SelectionKey全部清除,最后在执行SelectAgain

4. Reactor线程处理异步任务

处理异步任务有二种方式

  • ioRatio设置为100时,runAllTasks()处理所有异步任务。
  • ioRatio设置!=100时,runAllTasks(long timeoutNanos),在超时时间限定范围内,执行有限的异步任务

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4.1 runAllTasks()

protected boolean runAllTasks() {
    assert inEventLoop();
    boolean fetchedAll;
    boolean ranAtLeastOne = false;

    do {
        //将到达执行时间的定时任务转存到普通任务队列taskQueue中,统一由Reactor线程从taskQueue中取出执行
        fetchedAll = fetchFromScheduledTaskQueue();
        if (runAllTasksFrom(taskQueue)) {
            ranAtLeastOne = true;
        }
    } while (!fetchedAll); // keep on processing until we fetched all scheduled tasks.
    //fetchFromScheduledTaskQueue方法的返回值为true时表示到期的定时任务已经全部拉取出来并转存到普通任务队列中。
    //返回值为false时表示到期的定时任务只拉取出来一部分,因为这时普通任务队列已经满了,当执行完普通任务时,还需要在进行一次拉取。

    if (ranAtLeastOne) {
        //如果Reactor线程执行了至少一个异步任务,那么设置lastExecutionTime
        lastExecutionTime = ScheduledFutureTask.nanoTime();
    }
    //执行尾部队列任务
    afterRunningAllTasks();
    return ranAtLeastOne;
}
  • 将到达执行时间的定时任务取出并转存到普通任务队列taskQueue中。
  • Reactor线程统一从普通任务队列taskQueue中取出任务执行。
  • Reactor线程执行完定时任务普通任务后,开始执行存储于尾部任务队列tailTasks中的尾部任务

4.1.1 fetchFromScheduledTaskQueue

private boolean fetchFromScheduledTaskQueue() {
    //延迟任务队列为空直接返回
    if (scheduledTaskQueue == null || scheduledTaskQueue.isEmpty()) {
        return true;
    }
    //获取当前时间(距离启动reactor容器相对时间)
    long nanoTime = AbstractScheduledEventExecutor.nanoTime();
    for (;;) {
        //从定时任务队列中取出到达执行deadline的定时任务  deadline <= nanoTime
        Runnable scheduledTask = pollScheduledTask(nanoTime);
        if (scheduledTask == null) {
            return true;
        }
        if (!taskQueue.offer(scheduledTask)) {
            // No space left in the task queue add it back to the scheduledTaskQueue so we pick it up again.
            // taskQueue没有空间容纳 则在将定时任务重新塞进定时任务队列中等待下次执行
            scheduledTaskQueue.add((ScheduledFutureTask<?>) scheduledTask);
            return false;
        }
    }
}
  1. 获取当前时间(距离启动reactor容器相对时间)
protected static long nanoTime() {
    return ScheduledFutureTask.nanoTime();
}
static long nanoTime() {
    return System.nanoTime() - START_TIME;
}
  1. 从定时任务中找出scheduledTask.deadlineNanos() - nanoTime > 0的任务,也就是到达当前时间的任务。
protected final Runnable pollScheduledTask(long nanoTime) {
    assert inEventLoop();
    //从定时队列中取出要执行的定时任务  deadline <= nanoTime
    ScheduledFutureTask<?> scheduledTask = peekScheduledTask();
    //没有到达执行时间返回null
    if (scheduledTask == null || scheduledTask.deadlineNanos() - nanoTime > 0) {
        return null;
    }
    //符合取出条件 则取出
    scheduledTaskQueue.remove();
    scheduledTask.setConsumed();
    return scheduledTask;
}
  1. 将到期任务存入普通任务队列中,空间不够则继续存入延迟任务队列中。等待下次拉取。

    if (!taskQueue.offer(scheduledTask)) {
        // No space left in the task queue add it back to the scheduledTaskQueue so we pick it up again.
        // taskQueue没有空间容纳 则在将定时任务重新塞进定时任务队列中等待下次执行
        scheduledTaskQueue.add((ScheduledFutureTask<?>) scheduledTask);
        return false;
    }
    
  2. fetchFromScheduledTaskQueue为true表示延迟任务已经全部拉取成功则不在拉取。接着执行普通任务。

4.1.2 runAllTasksFrom

protected final boolean runAllTasksFrom(Queue<Runnable> taskQueue) {
    Runnable task = pollTaskFrom(taskQueue);
    if (task == null) {
        return false;
    }
    for (;;) {
        safeExecute(task);
        task = pollTaskFrom(taskQueue);
        if (task == null) {
            return true;
        }
    }
}
  1. 从队列中拉取一个任务
protected static Runnable pollTaskFrom(Queue<Runnable> taskQueue) {
    for (;;) {
        Runnable task = taskQueue.poll();
        if (task != WAKEUP_TASK) {
            return task;
        }
    }
}
  1. Reactor线程执行异步任务
protected static void safeExecute(Runnable task) {
    try {
        task.run();
    } catch (Throwable t) {
        logger.warn("A task raised an exception. Task: {}", task, t);
    }
}

返回值表示是否执行了任务。执行任务之后会设置ranAtLeastOne为true。接着会设置lastExecutionTime。并返回ranAtLeastOne。

4.1.3 afterRunningAllTasks

执行尾部任务同上。

@Override
protected void afterRunningAllTasks() {
    runAllTasksFrom(tailTasks);
}

4.2 runAllTasks(long timeoutNanos)

//如果运行时间超过 timeoutNanos,则返回。
protected boolean runAllTasks(long timeoutNanos) {
    //从定时任务队列中取出达到deadline执行时间的定时任务
    //将定时任务 转存到 普通任务队列taskQueue中
    fetchFromScheduledTaskQueue();
    Runnable task = pollTask();
    if (task == null) {
        //普通队列中没有任务时  执行队尾队列的任务
        afterRunningAllTasks();
        return false;
    }

    //异步任务执行超时deadline
    final long deadline = timeoutNanos > 0 ? ScheduledFutureTask.nanoTime() + timeoutNanos : 0;
    long runTasks = 0;
    long lastExecutionTime;
    for (;;) {
        safeExecute(task);//执行任务

        runTasks ++;

        // Check timeout every 64 tasks because nanoTime() is relatively expensive.
        // XXX: Hard-coded value - will make it configurable if it is really a problem.
        //每运行64个异步任务 检查一下 是否达到 执行deadline
        if ((runTasks & 0x3F) == 0) {
            lastExecutionTime = ScheduledFutureTask.nanoTime();
            //到达异步任务执行超时deadline,停止执行异步任务
            if (lastExecutionTime >= deadline) {
                break;
            }
        }

        task = pollTask();
        if (task == null) {
            lastExecutionTime = ScheduledFutureTask.nanoTime();
            break;
        }
    }
    //普通队列中没有任务时  执行队尾队列的任务
    afterRunningAllTasks();
    this.lastExecutionTime = lastExecutionTime;
    return true;
}
  1. 从定时任务队列中取出达到deadline执行时间的定时任务,存储普通任务队列。

  2. 计算超时时间。

  3. 每执行64个任务判断是否超过了执行时间。

5. 解决JDK Epoll空轮询BUG

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

if (ranTasks || strategy > 0) {
    if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS && logger.isDebugEnabled()) {
        logger.debug("Selector.select() returned prematurely {} times in a row for Selector {}.",
                selectCnt - 1, selector);
    }
    selectCnt = 0;
} else if (unexpectedSelectorWakeup(selectCnt)) { // Unexpected wakeup (unusual case)
//没有执行任何异步任务和IO就绪的事件,说明被意外唤醒。
    //既没有IO就绪事件,也没有异步任务,Reactor线程从Selector上被异常唤醒 触发JDK Epoll空轮训BUG
    //重新构建Selector,selectCnt归零
    selectCnt = 0;
}
  • ranTasks:表示是否执行过一次异步任务
  • strategy:JDK NIO Selector的select方法的返回值,用来表示IO就绪的Channel个数。

ranTasks = false 并且 strategy = 0这代表Reactor线程本次既没有异步任务执行也没有IO就绪Channel需要处理却被意外的唤醒。等于是空转了一圈。

    private static final int SELECTOR_AUTO_REBUILD_THRESHOLD;

 static {
        int selectorAutoRebuildThreshold = SystemPropertyUtil.getInt("io.netty.selectorAutoRebuildThreshold", 512);
        SELECTOR_AUTO_REBUILD_THRESHOLD = selectorAutoRebuildThreshold;

    }

private boolean unexpectedSelectorWakeup(int selectCnt) {
    if (Thread.interrupted()) {
        // Thread was interrupted so reset selected keys and break so we not run into a busy loop.
        // As this is most likely a bug in the handler of the user or it's client library we will
        // also log it.
        //
        // See https://github.com/netty/netty/issues/2426
        if (logger.isDebugEnabled()) {
            logger.debug("Selector.select() returned prematurely because " +
                    "Thread.currentThread().interrupt() was called. Use " +
                    "NioEventLoop.shutdownGracefully() to shutdown the NioEventLoop.");
        }
        return true;
    }
    //如果由于空循环导致selectCnt>=512,则重构selector
    if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&
            selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
        // The selector returned prematurely many times in a row.
        // Rebuild the selector to work around the problem.
        logger.warn("Selector.select() returned prematurely {} times in a row; rebuilding Selector {}.",
                selectCnt, selector);
        //重构selector
        rebuildSelector();
        return true;
    }
    return false;
}
  • 当空转次数累计512次时,会重建selector选择器。接着将空转次数重置0。这里512可以通过io.netty.selectorAutoRebuildThreshold 设置。

  • 如果selectCnt小于SELECTOR_AUTO_REBUILD_THRESHOLD,则返回不做任何处理,selectCnt继续计数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值