一、引言
在上篇文章中我们讨论了NioEventLoop/NioEventLoopGroup的构造过程,这篇文章我们来分析NioEventLoop/NioEventLoopGroup的运行过程。
NioEventLoop/NioEventLoopGroup的构造流程:
https://blog.csdn.net/abc123lzf/article/details/83245134
二、源码解析
在Netty中,NioEventLoop(包括所有的SingleThreadEventExecutor子类)有两个职责:一是作为IO线程处理IO事件,二是作为普通的线程处理通过execute等方法提交上来的任务。
一个NioEventLoop实例包含以下字段(Field):
修饰符 | 字段名 | 作用 |
---|---|---|
Queue<ScheduledFutureTask<?>> | scheduledTaskQueue | 该EventExecutor存放待执行的定时任务的任务队列 |
private final EventExecutorGroup | parent | 该EventLoop所属的EventExecutorGroup(EventLoopGroup) |
private final Queue <Runnable> | taskQueue | 存放待执行任务的队列 |
private final Thread | thread | 该EventLoop对应的线程,在构造方法中通过线程工厂构造 |
private final ThreadProperties | threadProperties | 存储线程属性的对象(例如是否为后台线程、线程优先级等) |
private final Semaphore | threadLock | 信号量,在这里用于加锁,初始值为new Semaphore(0) |
private final boolean | addTaskWakeUp | 是否在添加任务后立刻唤醒线程 |
private final int | maxPendingTasks | 最大保存的尚未执行任务的数量,超出这个数量后继续提交任务会转交给拒绝执行处理器处理 |
private final RejectedExecutionHandler | rejectedExecutionHandler | 拒绝执行处理器,用于处理当前EventLoop拒绝执行的任务 |
private long | lastExecutionTime | 线程运行的时间点 |
private volatile int | state | EventLoop的状态,初始值是ST_NOT_STARTED |
private volatile long | gracefulShutdownQuietPeriod | 时间相关变量,用于关闭EventLoop |
private volatile long | gracefulShutdownTimeout | 时间相关变量,用于关闭EventLoop |
private volatile long | gracefulShutdownStartTime | 时间相关变量,用于关闭EventLoop |
private final Promise<?> | terminationFuture | Promise实现类,用于异步操作 |
private Selector | selector | Selector对象,一般为SelectedSelectionKeySetSelector对象,也可能就是unwrappedSelector |
private Selector | unwrappedSelector | 未经包装的Selector选择器,由SelectorProvider对象的openSelector方法获得 |
private SelectedSelectionKeySet | selectedKeys | 存储该Selector所有的SelectorKey的对象 |
private final SelectorProvider | provider | 构造时指定的SelectorProvider,用于生成Selector对象 |
private final AtomicBoolean | wakenUp | 是否唤醒Selector |
private final SelectStrategy | selectStrategy | Selector的选择策略 |
private final IntSupplier | selectNowSupplier | 用于SelectStrategy对象 |
private final Callable<Integer> | pendingTasksCallable | Callable任务对象,任务逻辑为获取任务队列的长度 |
private volatile int | ioRatio | 事件循环中IO操作所需时间的百分比,默认为50 |
private int | cancelledKeys | 持有的SelectionKey对象被取消的数量 |
private boolean | needsToSelectAgain | 是否需要重新选择 |
当我们调用NioEventLoop的execute方法提交任务时,线程随即会被启动。
execute方法在服务端中会被很多个地方调用,比如AbstractUnsafe中的方法,DefaultPromise的notifyListeners方法、AbstractBootstrap的doBind0方法等。
@Override
public void execute(Runnable task) {
if (task == null)
throw new NullPointerException("task");
//当前线程是不是就是这个NioEventLoop持有的线程
boolean inEventLoop = inEventLoop();
//如果是就直接往任务队列中添加任务
if (inEventLoop) {
addTask(task);
//否则尝试启动这个线程并添加到任务队列中
} else {
startThread();
addTask(task);
//如果线程池已经被关闭,那么移除这个任务对象,然后抛出RejectedExecutionException
if (isShutdown() && removeTask(task)) {
reject();
}
}
if (!addTaskWakesUp && wakesUpForTask(task)) {
wakeup(inEventLoop);
}
}
execute方法首先判断当前线程是不是这个NioEventLoop持有的线程,如果是,就直接将Runnable任务对象添加到任务队列中。如果不是,就启动当前线程,再将任务添加到任务队列中。
startThread方法:
private void startThread() {
if (state == ST_NOT_STARTED) { //如果线程没有启动
//通过CAS改变state变量,然后调用start方法启动线程
if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
thread.start();
}
}
}
成员变量thread的实例化在SingleThreadEventExecutor构造阶段:
thread = threadFactory.newThread(new Runnable() {
@Override
public void run() {
boolean success = false;
//更新这个线程开始运行的时间
updateLastExecutionTime();
try {
//调用SingleThreadEventExecutor的run方法
SingleThreadEventExecutor.this.run();
success = true;
} catch (Throwable t) {
logger.warn("Unexpected exception from an event executor: ", t);
} finally {
//省略线程即将死亡后的处理...
}
}
});
SingleThreadEventExecutor的run方法在SingleThreadEventExecutor是一个抽象方法,其具体实现在NioEventLoop中:
@Override
protected void run() {
for (;;) {
try {
//通过选择策略器决定是否执行任务
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.SELECT:
//将wakenUp变量设为false,并调用select方法
select(wakenUp.getAndSet(false));
//如果此时wakenUp为true,那么中断此时调用Selector的selector方法的线程
if (wakenUp.get()) {
selector.wakeup();
}
default:
}
cancelledKeys = 0;
needsToSelectAgain = false;
final int ioRatio = this.ioRatio;
if (ioRatio == 100) { //如果IO操作时间百分比为100,那么就默认处理所有任务
try {
//优先处理SelectorKey
processSelectedKeys();
} finally {
//运行所有任务
runAllTasks();
}
} else {
final long ioStartTime = System.nanoTime();
try {
processSelectedKeys();
} finally {
final long ioTime = System.nanoTime() - ioStartTime;
//执行任务的时间为ioTime的(100-ioRation)%
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
}
} catch (Throwable t) {
handleLoopException(t);
}
try {
if (isShuttingDown()) {
closeAll();
if (confirmShutdown())
return;
}
} catch (Throwable t) {
handleLoopException(t);
}
}
}
run方法执行步骤可以分为:
1、首先调用构造时指定的选择策略器的calculateStrategy方法。
一般来说SelectStrategy都是采用默认的DefaultSelectStrategyFactory.INSTANCE返回的实例:
final class DefaultSelectStrategy implements SelectStrategy {
static final SelectStrategy INSTANCE = new DefaultSelectStrategy();
private DefaultSelectStrategy() { } //单例模式
@Override
public int calculateStrategy(IntSupplier selectSupplier, boolean hasTasks) throws Exception {
return hasTasks ? selectSupplier.get() : SelectStrategy.SELECT;
}
}
如果此时任务队列为空,那么直接返回SelectStrategy.SELECT,否则返回selectSupplier变量的get方法的返回值
对于IntSupplier,NioEventLoop实现了自己的IntSupplier:
private final IntSupplier selectNowSupplier = new IntSupplier() {
@Override
public int get() throws Exception {
return selectNow();
}
};
int selectNow() throws IOException {
try {
return selector.selectNow();
} finally {
if (wakenUp.get()) {
selector.wakeup();
}
}
}
IntSupplier通过调用Selector的selectNow方法以非阻塞的方式来获知有多少个Channel通道就绪。在方法返回前,如果将wakenUp的值设为true,那么调用wakeup方法中断调用select方法被阻塞的线程。
如果calculateStrategy方法返回的是SelectStrategy.SELECT,那么将wakenUp的值改为false,然后调用select方法。select方法通过一个循环等待有就绪的Channel通道,或者两个任务队列不为空时才会退出循环继续执行,如果循环次数超过了512次,那么就会调用rebuildSelector方法重构Selector。
2、调用processSelectedKeys方法处理Selector上注册的Channel,即处理IO事件
3、调用runAllTasks方法处理其它提交的任务
1、IO事件的处理机制
NioEventLoop的IO事件处理集成了ServerSocketChannel和ServerSocket的IO事件处理。
private void processSelectedKeys() {
if (selectedKeys != null) {
processSelectedKeysOptimized();
} else {
processSelectedKeysPlain(selector.selectedKeys());
}
}
如果在初始化过程中设置了selectedKeys,那么调用processSelectedKeysOptimized处理注册的Channel(在绝大多数运行环境下都会执行这个方法,也就是selectedKeys不会为null,所以限于篇幅不介绍processSelectedKeysPlain方法了):
private void processSelectedKeysOptimized() {
//遍历这个Selector所有的SelectorKeys
for (int i = 0; i < selectedKeys.size; ++i) {
final SelectionKey k = selectedKeys.keys[i];
//移除这个SelectionKey
selectedKeys.keys[i] = null;
//获取这个SelectionKey的附加对象
final Object a = k.attachment();
//如果这个附加对象是Netty的AbstractNioChannel的子类
if (a instanceof AbstractNioChannel) {
processSelectedKey(k, (AbstractNioChannel) a);
//否则就是NioTask的实现类
} else {
@SuppressWarnings("unchecked")
NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
processSelectedKey(k, task);
}
if (needsToSelectAgain) {
selectedKeys.reset(i + 1);
selectAgain();
i = -1;
}
}
}
简单地说,processSelectedKeysOptimized方法通过一个循环遍历selectedKeys中的SelectionKey集合获取就绪的IO事件,然后调用processSelectedKey方法处理这些IO事件。
这个方法对每个SelectionKey都调用了它的attachment方法获取它的附加对象,这个附加对象要么就是AbstractNioChannel,要么就是NioTask接口实现类。那么这个附加对象是在什么时候设置的呢?
答案就在AbstractNioChannel的doRegister方法中:
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
这个方法会在引导过程中被调用。
这个Channel和SelectionKey的关系就是这个SelectionKey属于这个Channel。
回到processSelectedKeysOptimized方法,我们再来看processSelectedKey方法是如何处理IO事件的:
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
final NioUnsafe unsafe = ch.unsafe(); //获取它的Unsafe对象
//如果这个SelectionKey不是有效的(在SelectionKey被取消,通道关闭或选择器关闭之前,SelectionKey都是有效的)
if (!k.isValid()) {
final EventLoop eventLoop;
try {
eventLoop = ch.eventLoop();
} catch (Throwable ignored) {
return;
}
if (eventLoop != this || eventLoop == null)
return;
unsafe.close(unsafe.voidPromise());
return;
}
try {
int readyOps = k.readyOps(); //获取已经准备就绪的操作事件
//如果为连接建立事件
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
int ops = k.interestOps();
//从事件集中移除这个事件
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops);
//通知Unsafe构建连接
unsafe.finishConnect();
}
//如果通道是可写的
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
ch.unsafe().forceFlush();
}
//如果通道是可读的或者是可以接受连接的,那么调用Unsafe的read方法
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
}
} catch (CancelledKeyException ignored) {
unsafe.close(unsafe.voidPromise());
}
}
processSelectedKey可以处理SelectionKey所有的IO事件:OP_ACCEPT,OP_CONNECT,OP_READ,OP_WRITE
OP_CONNECT事件的处理
对于SelectionKey的OP_CONNECT事件,processSelectedKey方法会调用Unsafe实现类的finishConnect建立连接(该方法位于AbstractNioChannel的AbstractNioUnsafe类中):
@Override
public final void finishConnect() {
assert eventLoop().inEventLoop();
try {
boolean wasActive = isActive();
doFinishConnect();
fulfillConnectPromise(connectPromise, wasActive);
} catch (Throwable t) {
fulfillConnectPromise(connectPromise, annotateConnectException(t, requestedRemoteAddress));
} finally {
if (connectTimeoutFuture != null) {
connectTimeoutFuture.cancel(false);
}
connectPromise = null;
}
}
doFinishConnect实现在子类NioSocketChannel中:
@Override
protected void doFinishConnect() throws Exception {
if (!javaChannel().finishConnect()) {
throw new Error();
}
}
doFinishConnect方法调用了java.nio.SocketChannel的finishConnect方法确认通道连接已经建立。
private void fulfillConnectPromise(ChannelPromise promise, boolean wasActive) {
if (promise == null)
return;
boolean active = isActive();
boolean promiseSet = promise.trySuccess();
if (!wasActive && active)
pipeline().fireChannelActive();
if (!promiseSet)
close(voidPromise());
}
fulfillConnectPromise方法首先通知DefaultChannelPromise的监听器连接已成功建立,然后向管道中的ChannelInboundHandler传播Channel Active事件(即调用管道的fireChannelActive方法)
OP_READ和OP_ACCPET事件的处理
虽然看上去OP_READ和OP_ACCPET事件都采用了read方法处理,但需要注意的是,只有当当前的NioEventLoop持有ServerSocketChannel时,它的SelectionKey才会发出OP_ACCPET事件,,那么它调用的read方法来自于NioMessageUnsafe(AbstractNioMessageChannel的内部类)。否则,则是SocketChannel发出的OP_READ事件,那么read方法来自NioByteUnsafe类(AbstractNioByteChannel的内部类)。
我们先从NioMessageUnsafe的read方法开始分析(即ServerSocketChannel处理OP_ACCEPT事件):
@Override
public void read() {
assert eventLoop().inEventLoop();
final ChannelConfig config = config();
if (!config.isAutoRead() && !isReadPending()) {
//从事件集中移除这个事件
removeReadOp();
return;
}
//获取每个循环读取的最大消息数(SocketChannel)
final int maxMessagesPerRead = config.getMaxMessagesPerRead();
final ChannelPipeline pipeline = pipeline();
boolean closed = false;
Throwable exception = null;
try {
try {
for (;;) {
//将对应的SocketChannel添加到readBuf中,返回1
int localRead = doReadMessages(readBuf);
if (localRead == 0) {
break;
}
if (localRead < 0) {
closed = true;
break;
}
if (!config.isAutoRead())
break;
//如果readBuf的长度大于maxMessagesPerRead,退出循环
if (readBuf.size() >= maxMessagesPerRead)
break;
}
} catch (Throwable t) {
exception = t;
}
setReadPending(false);
//将SocketChannel传递给ChannelInboundHandler
int size = readBuf.size();
for (int i = 0; i < size; i ++)
pipeline.fireChannelRead(readBuf.get(i));
//清除这些读取到的ByteBuf,并调用ChannelInboundHandler的fireChannelReadComplete方法
readBuf.clear();
pipeline.fireChannelReadComplete();
//如果发生异常,调用ChannelInboundHandler的fireExceptionCaught方法
if (exception != null) {
closed = closeOnReadError(exception);
pipeline.fireExceptionCaught(exception);
}
if (closed)
if (isOpen())
close(voidPromise());
} finally {
//再次检查这个事件有没有从事件集中去除
if (!config.isAutoRead() && !isReadPending()) {
removeReadOp();
}
}
}
NioMessageUnsafe维护了一个List集合readBuf,readBuf存放了ServerSocketChannel获取的SocketChannel,每个SocketChannel对应一个客户端连接。下面来分析read方法主要过程:
1、调用doReadMessages方法获取客户端连接的SocketChannel,并添加到readBuf中:
@Override
protected int doReadMessages(List<Object> buf) throws Exception {
SocketChannel ch = SocketUtils.accept(javaChannel());
try {
//将ServerSocketChannel和NioServerSocketChannel封装为一个
//NioSocketChannel对象,然后添加到readBuf中
if (ch != null) {
buf.add(new NioSocketChannel(this, ch));
return 1;
}
} catch (Throwable t) { //发生异常则关闭Channel
logger.warn("Failed to create a new channel from an accepted socket.", t);
try {
ch.close();
} catch (Throwable t2) {
logger.warn("Failed to close a socket.", t2);
}
}
return 0;
}
2、调用这个ServerSocketChannel所属的ChannelPipleine的fireChannelRead方法,产生一个进站事件,通过第一个注册的进站处理器(ChannelInboundHandler)开始流经管道,流经管道的msg为SocketChannel实例。对于具有bossGroup和workGroup的服务端程序来说,其流经的ChannelInboundHandler的顺序为:
HeadContext -> ServerBootstrap内部类ServerBootstrapAcceptor -> 用户自定义管道 ->TailContext
3、调用这个ServerSocketChannel所属的ChannelPipleine的fireChannelReadComplete方法,产生一个进站处理完成事件,同样通过第一个注册的进站处理器(ChannelInboundHandler)开始流经管道,顺序同上。
4、如果在获取SocketChannel发生异常,那么产生一个异常事件,将这个异常对象通过ChannelPipeline的fireExceptionCaught方法传递给ChannelInboundHandler。
分析完ServerSocketChannel的read方法处理流程后,我们再来分析SocketChannel的处理流程,即NioByteUnsafe的read方法:
@Override
public final void read() {
final ChannelConfig config = config();
//如果这个方法是手动调用的(默认是自动调用的),那么移除这个OP_READ事件并返回
if (!config.isAutoRead() && !isReadPending()) {
removeReadOp();
return;
}
//获取持有的管道、缓冲区分配器
final ChannelPipeline pipeline = pipeline();
final ByteBufAllocator allocator = config.getAllocator();
//获取最大的尝试读取次数
final int maxMessagesPerRead = config.getMaxMessagesPerRead();
RecvByteBufAllocator.Handle allocHandle = this.allocHandle;
if (allocHandle == null) {
this.allocHandle = allocHandle = config.getRecvByteBufAllocator().newHandle();
}
ByteBuf byteBuf = null;
int messages = 0;
boolean close = false;
try {
int totalReadAmount = 0;
boolean readPendingReset = false;
do {
//通过缓冲区分配器创建一个ByteBuf缓冲区
byteBuf = allocHandle.allocate(allocator);
//返回分配的缓冲区大小
int writable = byteBuf.writableBytes();
//向缓冲区读入Channel包含的可读数据,返回读入字节数
int localReadAmount = doReadBytes(byteBuf);
//如果没有可读入的数据或者读取失败,那么释放ByteBuf
if (localReadAmount <= 0) {
byteBuf.release();
byteBuf = null;
close = localReadAmount < 0;
if (close)
setReadPending(false);
break;
}
if (!readPendingReset) {
readPendingReset = true;
setReadPending(false);
}
//向管道传入持有读入数据的ByteBuf对象
pipeline.fireChannelRead(byteBuf);
byteBuf = null;
//防止数字溢出
if (totalReadAmount >= Integer.MAX_VALUE - localReadAmount) {
totalReadAmount = Integer.MAX_VALUE;
break;
}
totalReadAmount += localReadAmount;
if (!config.isAutoRead())
break;
//如果没有数据可读了,那么退出循环
if (localReadAmount < writable)
break;
//如果循环次数小于最大的尝试读取次数,那么继续尝试读取
} while (++messages < maxMessagesPerRead);
//读取完毕,向管道发出读入完成事件
pipeline.fireChannelReadComplete();
//记录本次读取的字节数量,在下次读入时分配一个更合适的缓冲区大小避免浪费内存或多次分配内存
allocHandle.record(totalReadAmount);
//如果通道关闭
if (close) {
closeOnRead(pipeline);
close = false;
}
} catch (Throwable t) {
//如果有异常抛出,那么向管道发出读入完成事件和读入异常事件
handleReadException(pipeline, byteBuf, t, close);
} finally {
//保证OP_READ事件已经移除
if (!config.isAutoRead() && !isReadPending())
removeReadOp();
}
}
read方法步骤如下:
1、获取这个Channel持有的管道、缓冲区分配器。
2、通过缓冲区分配器分配一个ByteBuf缓冲区
3、通过就绪的NIO Channel通道读入到缓冲区。读入成功后,向管道发出channelRead事件并将包含读入数据的缓冲区实例传入,如果已经没有可读入的数据了(读入字节数小于缓冲区可写容量),那么退出循环。
4、如果循环次数小于最大的尝试读取次数,那么从第2步开始继续循环。
5、向管道发出读入完成事件(通过调用fireChannelReadComplete方法)
OP_WRITE事件
OP_WRITE事件只有SocketChannel才会发出。
对于OP_WRITE事件,会调用Unsafe实现类的forceFlush方法写入通道,forceFlush方法默认调用flush0方法:
protected void flush0() {
if (inFlush0) //如果正在写入,那么方法退出
return;
final ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
//如果出站处理器还尚未往缓冲区中写入数据,那么方法结束
if (outboundBuffer == null || outboundBuffer.isEmpty())
return;
inFlush0 = true;
if (!isActive()) { //如果Channel通道不可用
try {
if (isOpen()) //如果Channel是打开的,那么发出一个尚未连接的异常事件
outboundBuffer.failFlushed(FLUSH0_NOT_YET_CONNECTED_EXCEPTION, true);
else //否则发出已经关闭连接的异常的事件
outboundBuffer.failFlushed(FLUSH0_CLOSED_CHANNEL_EXCEPTION, false);
} finally {
inFlush0 = false;
}
return;
}
try { //调用doWrite写入数据
doWrite(outboundBuffer);
} catch (Throwable t) {
//省略异常处理代码...
} finally {
inFlush0 = false;
}
}
doWrite方法实现在AbstractNioByteChannel中:
@Override
protected void doWrite(ChannelOutboundBuffer in) throws Exception {
int writeSpinCount = -1;
boolean setOpWrite = false;
for (;;) {
//获取需要往Channel通道写入的数据
Object msg = in.current();
if (msg == null) {
clearOpWrite();
return;
}
//如果msg为ByteBuf缓冲区
if (msg instanceof ByteBuf) {
ByteBuf buf = (ByteBuf) msg;
//获取缓冲区存放的字节数
int readableBytes = buf.readableBytes();
if (readableBytes == 0) { //如果缓冲区为空,那么释放内存
in.remove();
continue;
}
boolean done = false;
long flushedAmount = 0;
//获取循环写入次数,默认为16
if (writeSpinCount == -1)
writeSpinCount = config().getWriteSpinCount();
//写入NIO Channel通道
for (int i = writeSpinCount - 1; i >= 0; i--) {
int localFlushedAmount = doWriteBytes(buf);
if (localFlushedAmount == 0) {
setOpWrite = true;
break;
}
flushedAmount += localFlushedAmount;
if (!buf.isReadable()) {
done = true;
break;
}
}
//通知ChannelPromise
in.progress(flushedAmount);
if (done)
in.remove();
else
break;
} else if (msg instanceof FileRegion) {
//省略FileRegion的处理
} else {
throw new Error();
}
}
incompleteWrite(setOpWrite);
}
2、一般任务的处理
回到NioEventLoop,当执行完IO事件时,就会调用runAllTasks执行一般任务:
protected boolean runAllTasks() {
boolean fetchedAll;
do {
fetchedAll = fetchFromScheduledTaskQueue();
Runnable task = pollTask(); //从任务队列中取出任务
if (task == null) //如果没有可执行的任务则返回false
return false;
//从任务队列中取出任务并执行任务
for (;;) {
try {
task.run();
} catch (Throwable t) {
logger.warn("A task raised an exception.", t);
}
task = pollTask();
if (task == null)
break;
}
} while (!fetchedAll);
lastExecutionTime = ScheduledFutureTask.nanoTime();
return true;
}
因为EventLoop既需要处理IO事件,也需要执行一般任务, 因此我们最好不要向EventLoop提交耗时任务,提交这些任务会导致IO事件处理时间下降,影响到系统的吞吐量。