Netty-EventLoop執行流程分析

EventLoop類繼承圖

上文提到了,NioServerSocketChannel將OP_Accept事件注冊到bossGroup的EventLoop的selector上,而EventLoop是一個繼承自java.util.concurrent.ScheduledExecutorService類可執行定時任務的Executor類。

其完整的繼承類圖如下:


diagram.png

EventLoop的run方法源碼

EventLoop不僅可以處理IO線程類,同時還可以執行一個定時或非定時的任務。EventLoop的run方法如下:

protected void run() {
    //創建之后就會被調用
    //之后會不斷輪詢
    for (;;) {
        try {
            //判斷是否有任務,有任務,返回continue,沒任務需要處理,則調用SELECT
            switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
                case SelectStrategy.CONTINUE:
                    continue;
                case SelectStrategy.SELECT:
                    select(wakenUp.getAndSet(false));
                    if (wakenUp.get()) {
                        selector.wakeup();
                    }
                    // fall through
                default:
            }
            cancelledKeys = 0;
            needsToSelectAgain = false;
            final int ioRatio = this.ioRatio;
            if (ioRatio == 100) {
                try {
                    processSelectedKeys();
                } finally {
                    // Ensure we always run tasks.
                    runAllTasks();
                }
            } else {
                final long ioStartTime = System.nanoTime();
                try {
                    processSelectedKeys();
                } finally {
                    // Ensure we always run tasks.
                    final long ioTime = System.nanoTime() - ioStartTime;//IO事件處理的時間
                    runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                }
            }
        } catch (Throwable t) {
            handleLoopException(t);
        }
        // Always handle shutdown even if the loop processing threw an exception.
        try {
            if (isShuttingDown()) {
                closeAll();
                if (confirmShutdown()) {
                    return;
                }
            }
        } catch (Throwable t) {
            handleLoopException(t);
        }
    }
}

ioRadio表示IO事件執行的時間和任務執行的時間的比值,默認為1:1,我們在創建NioEventLoopGroup的時候可以設置這個值。NioEventLoopGroup.setIoRatio()。
通過源碼可以看出,run方法內在不斷的進行輪詢,首先處理是否有已經觸發的io事件,有則進行執行,而在finally塊中進行runAllTasks(),其中一個帶參數的runAllTasks表示,在指定的時間內返回無論任務是否執行完成。而不帶參數的runAllTasks則沒有時間限制。

下面分別對IO事件processSelectedKeys()和任務處理:runAllTasks()方法進行分析:
EventLoop處理IO事件

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;
        }
    }
}
//進行事件的判斷和執行
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
    //....省略部分代碼
    try {
        int readyOps = k.readyOps();
        // Process OP_WRITE first as we may be able to write some queued buffers and so free memory.
        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();
        }
        // Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead
        // to a spin loop
        if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
            unsafe.read();
        }
    } catch (CancelledKeyException ignored) {
        unsafe.close(unsafe.voidPromise());
    }
}

根據以上代碼可以看到,當讀取到的事件是OP_Accept或者OP_Read事件時,調用unsafe.read()事件進行處理,unsafe是SocketChannel內部專門處理IO事件的類,不同的SocketChannel擁有不同的unsafe實例,在此我們只討論我們用到的socketChannel:NioServerSocketChannel、NioSocketChannel,下面是它們的類圖。


image2018-6-4 10_58_40.png

其中AbstractNioChannle中提供了內部NioUnsafe接口,在子類AbstractNioByteChannle和AbstractNioMessageChannle分別進行了實現,所以當我們處理IO事件并調用Unsafe.read()時,NioSocketChannel和NioServerSocketChannel調用的是不同的unsafe進行處理。

  • 其中NioServerSocketChannel由于處理的是Accept事件,所以 unsafe.read的處理主要是接收并創建NioSocketChannel然后fireChannelRead事件,而這個事件最終會被ServerBootstrapAcceptor中的ChannelRead所處理,channelRead中,將當前創建的NioSocketChannel注冊到childGroup中。源碼如下:
//服務端處理OP_ACCEPT事件public void channelRead(ChannelHandlerContext ctx, Object msg) {
 final Channel child = (Channel) msg;
 child.pipeline().addLast(childHandler);
 setChannelOptions(child, childOptions, logger);
 for (Entry<AttributeKey<?>, Object> e: childAttrs) {
 child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
 }
 try {//注冊Channel到childGroup
 childGroup.register(child).addListener(new ChannelFutureListener() {
 @Override
 public void operationComplete(ChannelFuture future) throws Exception {
 if (!future.isSuccess()) {
 forceClose(child, future.cause());
 }
 }
 });
 } catch (Throwable t) {
 forceClose(child, t);
 }
}
  • NioSocketChannel處理的事件是Read事件,所以主要讀取緩沖區的內容到Bytebuf,然后fireChannelRead事件,進行在pipeline上傳播。
    關于Pipeline上傳播,在下節中我們會討論,NioServerSocketChannel和NioSocketChannel的pipeline的ChannelHandler鏈

EventLoop執行Task

EventLoop除了執行io事件外,本身也會執行一些任務和定時任務,如下面的runAllTasks即為eventloop執行task的源碼:

protected boolean runAllTasks(long timeoutNanos) {
    fetchFromScheduledTaskQueue();
    Runnable task = pollTask();
    if (task == null) {
        return false;
    }

    final long deadline = ScheduledFutureTask.nanoTime() + timeoutNanos;
    long runTasks = 0;
    long lastExecutionTime;
    for (;;) {
        try {
            task.run();
        } catch (Throwable t) {
            logger.warn("A task raised an exception.", t);
        }

        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個任務,就判斷一次事件是否超時
        if ((runTasks & 0x3F) == 0) {
            lastExecutionTime = ScheduledFutureTask.nanoTime();
            if (lastExecutionTime >= deadline) {
                break;
            }
        }

        task = pollTask();
        if (task == null) {
            lastExecutionTime = ScheduledFutureTask.nanoTime();
            break;
        }
    }

    this.lastExecutionTime = lastExecutionTime;
    return true;
}

根據源碼可以看到,首先EventLoop會將將要執行的定時任務(拿到還未執行的deadlineTime小于當前時間的所有定時任務),加入當前taskQueue中,并對taskQueue進行輪詢處理,其中如果設置了處理的task的時間限制,則每處理64個task就會判斷一下是否超時,如果超時則退出執行。

之前提到EventLoop內的任務分為兩種,一種是可立即執行的taskQueue,另一種是定時執行的scheduledTaskQueue,當我們在handler中提交普通任務和定時任務時會分別加入這兩個隊列。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容