文章目录
三.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_READ
和OP_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:表示没有异步任务,switch逻辑分支进入SelectStrategy.SELECT分支
- 返回 = 0:有任务没有io就绪任务,switch逻辑分支进入
default分支
- 返回 > 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方法
计算定时任务的执行deadline
,deadline
的计算逻辑是当前时间点
+任务延时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 集合
保存在NioEventLoop
的private SelectedSelectionKeySet selectedKeys
字段中,方便Reactor线程
直接从这里获取IO就绪
的SelectionKey
。
在优化开关关闭的情况下,Netty会直接采用JDK NIO Selector
的默认实现。此时NioEventLoop
的selectedKeys
字段就会为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
。对于客户端读写事件(Read
,Write
)活跃时,这里的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
都属于AbstractNioChannel
。Channel
上的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事件
,在Channel
向Selector
注册完毕后,通过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
的APIcancel方法
,将Channel
从Selector
中取消掉。该方法调用后,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集合
中的SelectionKey
从Selector
中所有的KeySet
中移除**。这里的KeySet
包括Selector
用于存放就绪SelectionKey
的selectedKeys集合
,以及用于存放所有注册的Channel
对应的SelectionKey
的keys集合
。
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;
}
}
}
- 获取当前时间(距离启动reactor容器相对时间)
protected static long nanoTime() {
return ScheduledFutureTask.nanoTime();
}
static long nanoTime() {
return System.nanoTime() - START_TIME;
}
- 从定时任务中找出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;
}
-
将到期任务存入普通任务队列中,空间不够则继续存入延迟任务队列中。等待下次拉取。
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; }
-
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;
}
}
}
- 从队列中拉取一个任务
protected static Runnable pollTaskFrom(Queue<Runnable> taskQueue) {
for (;;) {
Runnable task = taskQueue.poll();
if (task != WAKEUP_TASK) {
return task;
}
}
}
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;
}
-
从定时任务队列中取出达到deadline执行时间的定时任务,存储普通任务队列。
-
计算超时时间。
-
每执行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
继续计数。