Netty源码4.1.22
继前一篇,下面该分析processSelectedKeys()
processSelectedKeys()处理IO事件
private void processSelectedKeys() {
if (selectedKeys != null) {
processSelectedKeysOptimized();
} else {
processSelectedKeysPlain(selector.selectedKeys());
}
}
这里涉及Netty对SelectedKeys的优化,NioEventLoop初始化过程中会调用openSelector()
创建Selector,若你没有设置io.netty.noKeySetOptimization
属性为true,则默认开启优化
......
final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();
......
//selectorImplClass为"sun.nio.ch.SelectorImpl"的class对象
// 获取该对象的两个字段:selectedKeys与publicSelectedKeys
Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys");
Throwable cause = ReflectionUtil.trySetAccessible(selectedKeysField, true);
if (cause != null) {
return cause;
}
cause = ReflectionUtil.trySetAccessible(publicSelectedKeysField, true);
if (cause != null) {
return cause;
}
selectedKeysField.set(unwrappedSelector, selectedKeySet);
publicSelectedKeysField.set(unwrappedSelector, selectedKeySet);
目的就是将sun.nio.ch.SelectorImpl
的selectedKeys与publicSelectedKeys
与selectedKeySet绑定
SelectorImpl:
protected Set<SelectionKey> selectedKeys = new HashSet();
private Set<SelectionKey> publicSelectedKeys;
protected SelectorImpl(SelectorProvider var1) {
super(var1);
if (Util.atBugLevel("1.4")) {
this.publicKeys = this.keys;
this.publicSelectedKeys = this.selectedKeys;
} else {
this.publicKeys = Collections.unmodifiableSet(this.keys);
this.publicSelectedKeys = Util.ungrowableSet(this.selectedKeys);
}
}
publicSelectedKeys与selectedKeys相同,或是其的不可增加版,调用selectedKeys()返回的就是return this.publicSelectedKeys;
这两个HashSet有何用?在执行Selector的select()方法时,如果与SelectionKey相关的事件发生了,这个SelectionKey就被加入到selected-keys集合中,就是selectedKeys与publicSelectedKeys。
来看看代替者SelectedSelectionKeySet
final class SelectedSelectionKeySet extends AbstractSet<SelectionKey> {
SelectionKey[] keys;
int size;
SelectedSelectionKeySet() {
keys = new SelectionKey[1024];
}
@Override
public boolean add(SelectionKey o) {
if (o == null) {
return false;
}
keys[size++] = o;
if (size == keys.length) {
increaseCapacity(); // 扩容
}
return true;
}
......
继承自AbstractSet,内部是一个数组,每次将元素加在尾部,复杂度为0(1),不支持remove,contains,iterator。HashSet是依赖HashMap实现的,添加操作的复杂度为O(lgn).
回到一开始进入到processSelectedKeysOptimized方法
private void processSelectedKeysOptimized() {
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
selectedKeys.keys[i] = null;
final Object a = k.attachment();
if (a instanceof AbstractNioChannel) {
processSelectedKey(k, (AbstractNioChannel) a);
} else {
@SuppressWarnings("unchecked")
NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
processSelectedKey(k, task);
}
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;
}
}
}
由于前面的优化已经,selectedKeys就是存放SelectionKey的集合。从数组中取出进行处理。
selectedKeys.keys[i] = null
,当channel关闭的时候,其selectionKey也需要回收,所以这里需要将数组对其的强引用释放。
接下来就要对SelectionKey进行处理,if (a instanceof AbstractNioChannel)
attachment为什么会是AbstractNioChannel类型?回到注册阶段,在AbstractNioChannel的doRegister()方法调用了JDK底层,将nio的SelectableChannel注册到nio的Selector上,并将AbstractNioChannel作为附属对象javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
这样在某个SelectableChannel有IO事件发生时,便可以通过其selectionKey获得NioSocketChannel或NioServerSocketChannel。
关于processSelectedKey
涉及到pipline,之后分析。
接下来
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;
}
关于needsToSelectAgain:它在NioEventLoop的run方法,也就是本篇所分析的processSelectedKeys()方法调用之前被设为false,那么什么时候变为true?
void cancel(SelectionKey key) {
key.cancel();
// 在NioEventLoop的run方法,每次IO事件处理(processSelectedKeys)
// 开始前归为0
cancelledKeys ++;
if (cancelledKeys >= CLEANUP_INTERVAL) { //256
cancelledKeys = 0;
needsToSelectAgain = true;
}
}
被cancel掉的selectionKey的数量大于等于256时,会将needsToSelectAgain设为true,回到上面,needsToSelectAgain为true,会首先调用selectedKeys.reset(i + 1);
将selectionKey的数组清空,随后selectAgain();
重新填充。
private void selectAgain() {
needsToSelectAgain = false;
try {
// 填充SelectionKey数组,指SelectedSelectionKeySet中的SelectionKey[] keys;
selector.selectNow();
} catch (Throwable t) {
logger.warn("Failed to update SelectionKeys.", t);
}
}
总结一下就是,当删除一定数量(256)的SelectionKey后,Netty采取的策略便是在处理SelectionKey时发现这一情况后,重新填充SelectionKey数组,保证现存SelectionKey的及时有效。
总结一下
netty的reactor线程第二步做的事情为处理IO事件,netty使用数组替换掉jdk原生的HashSet来保证IO事件的高效处理,每个SelectionKey上绑定了netty类AbstractChannel对象作为attachment,在处理每个SelectionKey的时候,就可以找到AbstractChannel,然后通过pipeline的方式将处理串行到ChannelHandler,回调到用户方法。
处理任务队列runAllTasks()
先来看看task的使用
一:用户定义的普通任务
ctx.channel().eventLoop().execute(new Runnable() {
@Override
public void run() {
//...
}
});
跟进到execute调用addTask方法,此时是在NioEventLoop的线程
protected void addTask(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
}
if (!offerTask(task)) {
reject(task);
}
}
final boolean offerTask(Runnable task) {
if (isShutdown()) {
reject();
}
return taskQueue.offer(task);
}
任务会被加入到taskQueue队列,taskQueue在NioEventLoop初始化过程中在SingleThreadEventExecutor构造函数被初始化,taskQueue = newTaskQueue(this.maxPendingTasks);
protected Queue<Runnable> newTaskQueue(int maxPendingTasks) {
return new LinkedBlockingQueue<Runnable>(maxPendingTasks);
}
taskQueue是个LinkedBlockingQueue。
二:外部线程调用channel各种方法
channel.write(...)
这里涉及pipline之后剖析,总之最后会调用到AbstractChannelHandlerContext
private void write(Object msg, boolean flush, ChannelPromise promise) {
AbstractChannelHandlerContext next = findContextOutbound();
final Object m = pipeline.touch(msg, next);
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
if (flush) {
next.invokeWriteAndFlush(m, promise);
} else {
next.invokeWrite(m, promise);
}
} else {
AbstractWriteTask task;
if (flush) {
task = WriteAndFlushTask.newInstance(next, m, promise);
} else {
task = WriteTask.newInstance(next, m, promise);
}
safeExecute(executor, task, promise, m);
}
}
这里进入到else逻辑,将其封装成一个writeTask,之后调用safeExecute,里面会调用executor.execute(runnable);
放入队列,LinkedBlockingQueue保证了操作的线程安全。
三:用户定义的定时任务
ctx.channel().eventLoop().schedule(new Runnable() {
@Override
public void run() {
}
}, 60, TimeUnit.SECONDS);
先将任务封装成ScheduledFutureTask
之后
<V> ScheduledFuture<V> schedule(final ScheduledFutureTask<V> task) {
if (inEventLoop()) {
scheduledTaskQueue().add(task);
} else {
execute(new Runnable() {
@Override
public void run() {
scheduledTaskQueue().add(task);
}
});
}
return task;
}
PriorityQueue<ScheduledFutureTask<?>> scheduledTaskQueue() {
if (scheduledTaskQueue == null) {
scheduledTaskQueue = new DefaultPriorityQueue<ScheduledFutureTask<?>>(
SCHEDULED_FUTURE_TASK_COMPARATOR,
// Use same initial capacity as java.util.PriorityQueue
11);
}
return scheduledTaskQueue;
}
private static final Comparator<ScheduledFutureTask<?>> SCHEDULED_FUTURE_TASK_COMPARATOR =
new Comparator<ScheduledFutureTask<?>>() {
@Override
public int compare(ScheduledFutureTask<?> o1, ScheduledFutureTask<?> o2) {
return o1.compareTo(o2);
}
};
关于scheduledTaskQueue 它是实体是
public final class DefaultPriorityQueue<T extends PriorityQueueNode>
extends AbstractQueue<T>implements PriorityQueue<T> {
它是Netty实现的优先级队列,它是非线程安全的,那么在外部线程并发向其添加任务时如何保证线程安全?Netty的做法是外部线程的添加操作会被封装成一个Runnable交给NioEventLoop的线程来实际执行,以此来保证安全性。
之前的文章中说scheduledTaskQueue
里的定时任务按照延迟时间从小到大排列,首先来了解下这里时间的变化,如例子中任务会在60s后执行
return schedule(new ScheduledFutureTask<Void>(this, command,
null, ScheduledFutureTask.deadlineNanos(unit.toNanos(delay))));
可以看到60s会先变换成纳秒之后被ScheduledFutureTask.deadlineNanos处理,
static long deadlineNanos(long delay) {
return nanoTime() + delay;
}
返回该任务的定时终止时间,该值会被赋给ScheduledFutureTask的deadlineNanos
字段
来看看ScheduledFutureTask
的比较策略
public int compareTo(Delayed o) {
if (this == o) {
return 0;
}
ScheduledFutureTask<?> that = (ScheduledFutureTask<?>) o;
long d = deadlineNanos() - that.deadlineNanos();
if (d < 0) {
return -1;
} else if (d > 0) {
return 1;
} else if (id < that.id) {
return -1;
} else if (id == that.id) {
throw new Error();
} else {
return 1;
}
}
终止时间在前的优先级高,在终止时间相同时按照id来排列,而id是按照加入队列的顺序递增的,即先加入的优先级高。
Netty中定时任务分为三类:
/* 0 - no repeat, >0 - repeat at fixed rate,
<0 - repeat with fixed delay */
private final long periodNanos;
periodNanos不同值代表不同类型,0代表只执行一次的定时任务;>0代表以固定频率执行的任务,无关任务的执行时间;<0代表以固定频率执行的任务,不过是在每次任务执行完后开始计时。
ScheduledFutureTask构造器分为两类
ScheduledFutureTask(
AbstractScheduledEventExecutor executor,
Callable<V> callable, long nanoTime, long period) {
super(executor, callable);
if (period == 0) {
throw new IllegalArgumentException("period: 0 (expected: != 0)");
}
deadlineNanos = nanoTime;
periodNanos = period;
}
ScheduledFutureTask(
AbstractScheduledEventExecutor executor,
Callable<V> callable, long nanoTime) {
super(executor, callable);
deadlineNanos = nanoTime;
periodNanos = 0;
}
你传入的period参数值代表执行间隔。
来看看ScheduledFutureTask对它们的不同处理
public void run() {
assert executor().inEventLoop();
try {
if (periodNanos == 0) {
if (setUncancellableInternal()) {
V result = task.call();
setSuccessInternal(result);
}
} else {
// check if is done as it may was cancelled
if (!isCancelled()) {
task.call();
if (!executor().isShutdown()) {
long p = periodNanos;
if (p > 0) {
deadlineNanos += p;
} else {
deadlineNanos = nanoTime() - p;
}
if (!isCancelled()) {
// scheduledTaskQueue can never be null as we lazy init it before submit the task!
Queue<ScheduledFutureTask<?>> scheduledTaskQueue =
((AbstractScheduledEventExecutor) executor()).scheduledTaskQueue;
assert scheduledTaskQueue != null;
scheduledTaskQueue.add(this);
}
}
}
}
} catch (Throwable cause) {
setFailureInternal(cause);
}
}
periodNanos == 0,执行任务然后退出;
periodNanos > 0代表每periodNanos时间执行一次,不考虑任务的耗时,所以下次任务的执行时间为deadlineNanos += p;
periodNanos < 0代表每periodNanos时间执行一次,每次以当此任务结束时间为开始计时,所以下次任务的执行时间为deadlineNanos = nanoTime() - p;
计算完下次任务的开始执行时间后,就将其重新加入队列,scheduledTaskQueue.add(this);
进入正题runAllTasks(long timeoutNanos)
由于final int ioRatio = this.ioRatio;
默认为50,NioEventLoop的run方法进入如下代码
final long ioStartTime = System.nanoTime();
......
try {
processSelectedKeys();
} finally {
// Ensure we always run tasks.
final long ioTime = System.nanoTime() - ioStartTime;
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
在IO事件处理前记录开始时间ioStartTime
,之后processSelectedKeys()
处理IO事件,如上面分析的,最后计算出本次处理所耗时间ioTime
,调用runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
runAllTasks(long timeoutNanos)
该方法会取出所有taskQueue中的任务,执行其run,但执行总时长不能超过timeoutNanos
,
protected boolean runAllTasks(long timeoutNanos) {
fetchFromScheduledTaskQueue();
Runnable task = pollTask();
if (task == null) {
afterRunningAllTasks();
return false;
}
final long deadline = ScheduledFutureTask.nanoTime() + timeoutNanos;
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.
if ((runTasks & 0x3F) == 0) {
lastExecutionTime = ScheduledFutureTask.nanoTime();
if (lastExecutionTime >= deadline) {
break;
}
}
task = pollTask();
if (task == null) {
lastExecutionTime = ScheduledFutureTask.nanoTime();
break;
}
}
afterRunningAllTasks();
this.lastExecutionTime = lastExecutionTime;
return true;
}
1,首先从scheduledTaskQueue转移定时任务到taskQueue,即fetchFromScheduledTaskQueue();
private boolean fetchFromScheduledTaskQueue() {
// 当前时间
long nanoTime = AbstractScheduledEventExecutor.nanoTime();
// 获取到期任务
Runnable scheduledTask = pollScheduledTask(nanoTime);
while (scheduledTask != null) {
if (!taskQueue.offer(scheduledTask)) { // 将其加入taskQueue
// 若加入失败,由于该定时任务已被从scheduledTaskQueue中删除,
// 这里重新加回
scheduledTaskQueue().add((ScheduledFutureTask<?>) scheduledTask);
return false;
}
// 继续获取超时发任务
scheduledTask = pollScheduledTask(nanoTime);
}
return true;
}
// 检查定时任务队列scheduledTaskQueue的第一个任务(最先到期的任务)是否到期,
// 到期就从scheduledTaskQueue中删除并返回它,否则返回null
protected final Runnable pollScheduledTask(long nanoTime) {
assert inEventLoop(); // 只能是NioEventLoop的线程才能做此操作
Queue<ScheduledFutureTask<?>> scheduledTaskQueue = this.scheduledTaskQueue;
ScheduledFutureTask<?> scheduledTask = scheduledTaskQueue == null ? null :
scheduledTaskQueue.peek();
if (scheduledTask == null) {
return null;
}
if (scheduledTask.deadlineNanos() <= nanoTime) {
scheduledTaskQueue.remove();
return scheduledTask;
}
return null;
}
2,获取一个taskQueue的任务,计算runAllTasks(long timeoutNanos)
方法的终止时间,
Runnable task = pollTask();
final long deadline = ScheduledFutureTask.nanoTime() + timeoutNanos;
3, 循环执行任务
long runTasks = 0;
long lastExecutionTime;
for (;;) {
//所谓safe就是直接调用task的run方法,让它在NioEventLoop线程中执行
safeExecute(task);
runTasks ++; // 记录循环次数
// Check timeout every 64 tasks because nanoTime() is relatively expensive.
// 由于nanoTime() 的耗时,所以Nety采取每循环64次就重新校验下
// 当前是否超过方法截至时间deadline
if ((runTasks & 0x3F) == 0) {
lastExecutionTime = ScheduledFutureTask.nanoTime();
if (lastExecutionTime >= deadline) {
break;
}
}
task = pollTask(); // 获取任务
if (task == null) { // 为null表明任务取完,记录下结束时间
lastExecutionTime = ScheduledFutureTask.nanoTime();
break;
}
}
4,taskQueue队列任务执行完后
afterRunningAllTasks();
// 记录本次runAllTasks方法结束时间
this.lastExecutionTime = lastExecutionTime;
return true;
关于this.lastExecutionTime
在多处被赋值,只在一个地方被使用到,即confirmShutdown
,它与Netty的优雅退出机制有关
Netty 优雅退出机制和原理
来看看 afterRunningAllTasks()
protected void afterRunningAllTasks() {
runAllTasksFrom(tailTasks);
}
// 取出队列中的任务,调用其run方法执行,直到队列为空
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;
}
}
}
关于tailTasks
同样是在NioEventLoop初始化过程中在SingleThreadEventLoop
构造器中被创建,tailTasks = newTaskQueue(maxPendingTasks);
,也是个LinkedBlockingQueue,该队列作用?
若你想在每次任务循环结束执行某些操作,可以调用NioEventLoop父类SingleThreadEventLoop的executeAfterEventLoopIteration
,它会将任务添加到tailTasks
,afterRunningAllTasks会对它进行调用。
public final void executeAfterEventLoopIteration(Runnable task) {
ObjectUtil.checkNotNull(task, "task");
if (isShutdown()) {
reject();
}
if (!tailTasks.offer(task)) {
reject(task);
}
if (wakesUpForTask(task)) {
wakeup(inEventLoop());
}
}