当前位置:首页 > Web开发 > 正文

4.Netty执行IO事件和非IO任务

2024-03-31 Web开发

回顾NioEventLoop的run方法流程

上文说到NioEventLoop的run方法可以分为3个步骤:

轮询channel中就绪的IO事件

处理轮询出的IO事件

处理所有任务,也包括定时任务

其中步骤1已在上一节讲述,这里接着讲述下面2个步骤

IO事件与非IO任务

首先看一下在步骤2和步骤3的主干代码

final int ioRatio = this.ioRatio; // 将所有任务执行完 if (ioRatio == 100) { try { processSelectedKeys(); } finally { // Ensure we always run tasks. runAllTasks(); } } else { // 记录IO事件消耗的时间,然后按比例处理分配时间处理非IO任务 final long ioStartTime = System.nanoTime(); try { processSelectedKeys(); } finally { // Ensure we always run tasks. final long ioTime = System.nanoTime() - ioStartTime; // ioRatio默认50,(100-ioRatio)/ioRatio刚好等于1,做到平均分配 runAllTasks(ioTime * (100 - ioRatio) / ioRatio); } }

ioRadio是NioEventLoop的一个成员变量,用来控制分配花费在IO事件与非IO任务时间的比例。默认情况下,ioRadio是50,表示IO事件与非IO任务
将分配相同时间。而当ioRatio为100时,该值失效,不再平衡两种动作的时间分配比值。
了解了这一点,上述两种分支代码就不难理解了,我们直接进入processSelectedKeys,看看netty如何执行IO事件

处理IO事件

先进入processSelectedKeys方法内部。

private void processSelectedKeys() { if (selectedKeys != null) { processSelectedKeysOptimized(); } else { processSelectedKeysPlain(selector.selectedKeys()); } }

可以看到这里又根据selectedKeys是否为空这个条件来确定是处理优化过的keys还是普通keys。关于selectedKeys,在NioEventLoop介绍这一节中,
我们介绍了NioEventLoop的创建,在创建过程中,默认会将SelectedKeys由Hashset替换为数组实现,此处的selectedKeys正是替换过后的实现。
我们继续跟进到processSelectedKeysOptimized方法

private void processSelectedKeysOptimized() { for (int i = 0; i < selectedKeys.size; ++i) { final SelectionKey k = selectedKeys.keys[i]; selectedKeys.keys[i] = null; final Object a = k.attachment(); if (a instanceof AbstractNioChannel) { processSelectedKey(k, (AbstractNioChannel) a); } else { NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a; processSelectedKey(k, task); } if (needsToSelectAgain) { selectedKeys.reset(i + 1); selectAgain(); i = -1; } } }

方法内部用一个for循环处理selectedKeys。key的attchment默认是在注册时附加上去的NioServerSocketChannel和NioSocketChannel。
继续跟进processSelectedKey(k, (AbstractNioChannel) a)方法。

private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) { final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe(); if (!k.isValid()) { final EventLoop eventLoop = ch.eventLoop(); if (eventLoop != this || eventLoop == null) { return; } unsafe.close(unsafe.voidPromise()); return; } int readyOps = k.readyOps(); if ((readyOps & SelectionKey.OP_CONNECT) != 0) { int ops = k.interestOps(); ops &= ~SelectionKey.OP_CONNECT; k.interestOps(ops); unsafe.finishConnect(); } if ((readyOps & SelectionKey.OP_WRITE) != 0) { ch.unsafe().forceFlush(); } if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) { unsafe.read(); } }

netty首先对selectionKey的有效性做了一个判断。当key无效时,关闭key所在的channel。当key有效时,委托NioUnsafe对象对key进行IO操作。
注意这里先进行OP_CONNECT,再执行OP_WRITE,最后执行OP_READ和OP_ACCEPT。关于Unsafe的这些IO操作留待以后分析。

processSelectedKeysPlain方法流程类似,略过

处理非IO任务

由于IoRatio默认为50,我们先进入runAllTasks(ioTime * (100 - ioRatio) / ioRatio)方法。

protected boolean runAllTasks(long timeoutNanos) { // 步骤1 fetchFromScheduledTaskQueue(); // 步骤2 Runnable task = pollTask(); if (task == null) { afterRunningAllTasks(); return false; } // 步骤3 final long deadline = ScheduledFutureTask.nanoTime() + timeoutNanos; long runTasks = 0; long lastExecutionTime; for (;;) { // 步骤4 safeExecute(task); runTasks ++; // 步骤5 if ((runTasks & 0x3F) == 0) { lastExecutionTime = ScheduledFutureTask.nanoTime(); if (lastExecutionTime >= deadline) { break; } } task = pollTask(); if (task == null) { lastExecutionTime = ScheduledFutureTask.nanoTime(); break; } } // 步骤6 afterRunningAllTasks(); this.lastExecutionTime = lastExecutionTime; return true; }

非IO任务的执行可以分为6个步骤

从定时任务队列聚合任务到普通任务队列

从普通队列中获取任务

计算任务执行的超时时间

安全执行任务

任务执行到一定次数,计算是否超时

执行完taskQueue普通队列里的任务后,再去执行tailTaskQueue里的任务。但目前暂时没有看到tailTaskQueue使用的地方,也许是一个扩展点吧,这里先略过。

我们一个一个步骤讲解

聚合定时任务到普通任务队列

温馨提示: 本文由Jm博客推荐,转载请保留链接: https://www.jmwww.net/file/web/41526.html