Netty核心組件之NioEventLoop(一)

開篇

NioEventLoop是Netty框架的Reactor線程;
NioEventLoop負(fù)責(zé)處理注冊(cè)在其上面的所有Channel的IO事件,通常情況下一個(gè)NioEventLoop會(huì)下掛多個(gè)Channel;
NioEventLoop同時(shí)會(huì)負(fù)責(zé)通過execute方法提交的任務(wù),以及通過schedule方法提交的定時(shí)任務(wù);

在接下來幾篇文章,我會(huì)通過Netty的源碼深入講解NioEventLoop的實(shí)現(xiàn)機(jī)制。
特別說明:基于4.1.52版本的源碼

類繼承關(guān)系以及重要的成員變量

先來看下NioEventLoop的類關(guān)系圖和重要的屬性,對(duì)其有一個(gè)整體的感知,便于后面詳細(xì)分析。

  • 類繼承關(guān)系
    NioEventLoop繼承體系.png

    可以看到NioEventLoop的繼承關(guān)系非常復(fù)雜,最上層是JDK的Executor接口,說明它歸根到底是一個(gè)執(zhí)行器,是用來執(zhí)行任務(wù)的。另外,它實(shí)現(xiàn)了EventLoop接口、EventExecutorGroup接口和ScheduledExecutorService接口,繼承了SingleThreadEventExecutor類,這些接口和類為這個(gè)執(zhí)行器添加了十分繁復(fù)的功能特性,要搞清楚NioEventLoop的具體實(shí)現(xiàn)機(jī)制就要不停的在這些父類和接口中來回跳轉(zhuǎn)。
  • 重要的成員變量
private Selector selector;
private SelectedSelectionKeySet selectedKeys;
private volatile Thread thread;
private final EventExecutorGroup parent;
private final Queue<Runnable> taskQueue;
PriorityQueue<ScheduledFutureTask<?>> scheduledTaskQueue;
private final Queue<Runnable> tailTasks;
  1. selector:作為NIO框架的Reactor線程,NioEventLoop需要處理網(wǎng)絡(luò)IO事件,因此它需要有一個(gè)多路復(fù)用器,即Java NIO的Selector對(duì)象;
  2. selectedKeys:每次select操作選出來的有事件就緒的SelectionKey集合,在NioEventLoop的run方法中會(huì)處理這些事件;
  3. thread:即每個(gè)NioEventLoop綁定的線程,它們是一對(duì)一的關(guān)系,一旦綁定,在整個(gè)生命周期內(nèi)都不會(huì)改變;
  4. parent:即當(dāng)前的NioEventLoop所屬的EventExecutorGroup;
  5. taskQueue:NioEventLoop中三大隊(duì)列之一,用于保存需要被執(zhí)行的任務(wù)。
  6. scheduledTaskQueue:NioEventLoop中三大隊(duì)列之一,是一個(gè)優(yōu)先級(jí)隊(duì)列(內(nèi)部其實(shí)是一個(gè)按照任務(wù)的下次執(zhí)行時(shí)間排序的小頂堆),用于保存定時(shí)任務(wù),當(dāng)檢測(cè)到定時(shí)任務(wù)需要被執(zhí)行時(shí),會(huì)將任務(wù)從scheduledTaskQueue中取出,放入taskQueue
  7. tailTasks:NioEventLoop中三大隊(duì)列之一,用于存儲(chǔ)當(dāng)前或下一次事件循環(huán)結(jié)束后需要執(zhí)行的任務(wù);

構(gòu)造函數(shù)

首先來看NioEventLoop的構(gòu)造函數(shù)

    NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
                 SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler,
                 EventLoopTaskQueueFactory queueFactory) {
        // 設(shè)置parent、executor、addTaskWakesUp(添加任務(wù)時(shí)是否喚醒select)、創(chuàng)建taskQueue和tailTask隊(duì) 
        // 列
        super(parent, executor, false, newTaskQueue(queueFactory), newTaskQueue(queueFactory),
                rejectedExecutionHandler);
        this.provider = ObjectUtil.checkNotNull(selectorProvider, "selectorProvider");
        this.selectStrategy = ObjectUtil.checkNotNull(strategy, "selectStrategy");
        // selector初始化
        final SelectorTuple selectorTuple = openSelector();
        this.selector = selectorTuple.selector;
        this.unwrappedSelector = selectorTuple.unwrappedSelector;
    }

在構(gòu)造函數(shù)中,會(huì)創(chuàng)建任務(wù)隊(duì)列和tailTask隊(duì)列

    private static Queue<Runnable> newTaskQueue(
            EventLoopTaskQueueFactory queueFactory) {
        if (queueFactory == null) {
            return newTaskQueue0(DEFAULT_MAX_PENDING_TASKS);
        }
        return queueFactory.newTaskQueue(DEFAULT_MAX_PENDING_TASKS);
    }

    private static Queue<Runnable> newTaskQueue0(int maxPendingTasks) {
        return maxPendingTasks == Integer.MAX_VALUE ? PlatformDependent.<Runnable>newMpscQueue()
                : PlatformDependent.<Runnable>newMpscQueue(maxPendingTasks);
    }

默認(rèn)情況下,會(huì)創(chuàng)建MPSC,即多生產(chǎn)者單消費(fèi)者的隊(duì)列,這里最終會(huì)用到JCTools庫,這里不過多介紹,感興趣的可以自己去了解。

構(gòu)造函數(shù)中還會(huì)初始化selector和根據(jù)配置對(duì)selectedKeys進(jìn)行優(yōu)化

    private SelectorTuple openSelector() {
        final Selector unwrappedSelector;
        try {
            unwrappedSelector = provider.openSelector();
        } catch (IOException e) {
            throw new ChannelException("failed to open a new selector", e);
        }
        // 如果優(yōu)化選項(xiàng)沒有開啟,則直接返回
        if (DISABLE_KEY_SET_OPTIMIZATION) {
            return new SelectorTuple(unwrappedSelector);
        }

        final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();
        Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
            @Override
            public Object run() {
                try {
                    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);
                    return null;
                } catch (NoSuchFieldException e) {
                    return e;
                } catch (IllegalAccessException e) {
                    return e;
                }
            }
        });
        selectedKeys = selectedKeySet;
        logger.trace("instrumented a special java.util.Set into: {}", unwrappedSelector);
        return new SelectorTuple(unwrappedSelector,
                                 new SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet));
    }

如果設(shè)置了優(yōu)化開關(guān)(默認(rèn)優(yōu)化選項(xiàng)是開啟的),則通過反射的方式從Selector中獲取selectedKeys和publicSelectedKeys,將這兩個(gè)成員設(shè)置為可寫,通過反射,使用Netty構(gòu)造的selectedKeySet將原生JDK的selectedKeys替換掉。
我們知道使用Java原生NIO接口時(shí),需要先調(diào)Selector的select方法,再調(diào)selectedKeys方法才可以獲得有IO事件準(zhǔn)備好的SelectionKey集合。這里優(yōu)化過后,只通過一步select調(diào)用,就可以從selectedKeySet獲得需要的SelectionKey集合。
另外,原生Java的SelectionKey集合是一個(gè)HashSet,這里優(yōu)化過后的SelectedSelectionKeySet底層是一個(gè)數(shù)組,效率更高。

run方法解析

EventLoop的職責(zé)可以用下面這張圖形象的表示

EventLoop線程

EventLoop的run方法在一個(gè)for死循環(huán)中,周而復(fù)始的做著三件事:

1、從已注冊(cè)的Channel監(jiān)聽I(yíng)O事件;
2、處理IO事件;
3、從任務(wù)隊(duì)列取任務(wù)執(zhí)行。

    protected void run() {
        int selectCnt = 0;
        for (;;) {
              int strategy;
              try {
                    // 計(jì)算本次循環(huán)的執(zhí)行策略
                    strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());
                    switch (strategy) {
                    case SelectStrategy.BUSY_WAIT:
                    case SelectStrategy.SELECT:
                      // 調(diào)用Java NIO的多路復(fù)用器,檢查注冊(cè)在NioEventLoop上的Channel的IO狀態(tài)
                      strategy = select(curDeadlineNanos);
                    }
                } catch (IOException e) {
                }
                // 處理IO事件
                processSelectedKeys();
                // 處理任務(wù)隊(duì)列中的任務(wù)
                ranTasks = runAllTasks();
                ...
    }

下面詳細(xì)解析:

  • 先來看calculateStrategy
    public int calculateStrategy(IntSupplier selectSupplier, boolean hasTasks) throws Exception {
        return hasTasks ? selectSupplier.get() : SelectStrategy.SELECT;
    }

    private final IntSupplier selectNowSupplier = new IntSupplier() {
        @Override
        public int get() throws Exception {
            return selectNow();
        }
    };

    protected boolean hasTasks() {
        assert inEventLoop();
        return !taskQueue.isEmpty() || !tailTasks.isEmpty();
    }

每次循環(huán),都會(huì)檢測(cè)任務(wù)隊(duì)列和IO事件,如果任務(wù)隊(duì)列中沒有任務(wù),則直接返回SelectStrategy.SELECT;如果任務(wù)隊(duì)列中有任務(wù),則會(huì)調(diào)用非阻塞的selectNow檢測(cè)有IO事件準(zhǔn)備好的Channel數(shù)。

  • 阻塞的select
    當(dāng)任務(wù)隊(duì)列中沒有任務(wù)時(shí),直接進(jìn)入select分支
                    case SelectStrategy.SELECT:
                        // 找到下一個(gè)將要執(zhí)行的定時(shí)任務(wù)的截止時(shí)間
                        long curDeadlineNanos = nextScheduledTaskDeadlineNanos();
                        if (curDeadlineNanos == -1L) {
                            curDeadlineNanos = NONE; // nothing on the calendar
                        }
                        nextWakeupNanos.set(curDeadlineNanos);
                        try {
                            if (!hasTasks()) {
                                // 阻塞調(diào)用select
                                strategy = select(curDeadlineNanos);
                            }
                        } finally {
                            nextWakeupNanos.lazySet(AWAKE);
                        }
                        // fall through

nextScheduledTaskDeadlineNanos方法返回下一個(gè)將要被執(zhí)行的定時(shí)任務(wù)的截止時(shí)間

    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;
    }

NioEventLoop的定時(shí)任務(wù)隊(duì)列是一個(gè)優(yōu)先級(jí)隊(duì)列,隊(duì)列中存儲(chǔ)的是ScheduledFutureTask對(duì)象

    PriorityQueue<ScheduledFutureTask<?>> scheduledTaskQueue() {
        if (scheduledTaskQueue == null) {
            scheduledTaskQueue = new DefaultPriorityQueue<ScheduledFutureTask<?>>(
                    SCHEDULED_FUTURE_TASK_COMPARATOR,
                    11);
        }
        return scheduledTaskQueue;
    }

通過ScheduledFutureTask的compareTo方法可以看出,優(yōu)先級(jí)隊(duì)列中的元素是以任務(wù)的截止時(shí)間來排序的,隊(duì)首元素的截止時(shí)間最小,當(dāng)截止時(shí)間相同時(shí),以任務(wù)ID排序,ID小的排在前面。

    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 {
            assert id != that.id;
            return 1;
        }
    }

當(dāng)定時(shí)任務(wù)ScheduledFutureTask執(zhí)行后,會(huì)根據(jù)periodNanos的取值決定是否要將任務(wù)重新放回隊(duì)列。從netty的注釋可以清晰看到:

當(dāng)periodNanos為0時(shí),表示的是只執(zhí)行一次的任務(wù),執(zhí)行完后丟棄就好,不再放回隊(duì)列;
當(dāng)periodNanos大于0時(shí)表示的是以固定的頻率執(zhí)行任務(wù),下一次任務(wù)執(zhí)行的開始時(shí)間是以上一次任務(wù)的開始時(shí)間為基準(zhǔn)而得來;
當(dāng)periodNanos小于0時(shí)表示的是以固定的延遲時(shí)間執(zhí)行任務(wù),下一次任務(wù)的開始時(shí)間是以上一次任務(wù)的結(jié)束時(shí)間為基準(zhǔn)而得來。

    /* 0 - no repeat, >0 - repeat at fixed rate, <0 - repeat with fixed delay */
    private final long periodNanos;

看下ScheduledFutureTask的run方法

    public void run() {
        assert executor().inEventLoop();
        try {
            if (delayNanos() > 0L) {
                // 執(zhí)行時(shí)間還未到,則要么移除任務(wù),要么重新加入隊(duì)列
                if (isCancelled()) {
                    scheduledExecutor().scheduledTaskQueue().removeTyped(this);
                } else {
                    scheduledExecutor().scheduleFromEventLoop(this);
                }
                return;
            }
            // 只執(zhí)行一次的任務(wù),執(zhí)行完后丟棄就好,不再放回隊(duì)列
            if (periodNanos == 0) {
                if (setUncancellableInternal()) {
                    V result = runTask();
                    setSuccessInternal(result);
                }
            } else {
                if (!isCancelled()) {
                    runTask();
                    if (!executor().isShutdown()) {
                        // 根據(jù)periodNanos ,計(jì)算截止時(shí)間
                        if (periodNanos > 0) {
                            deadlineNanos += periodNanos;
                        } else {
                            deadlineNanos = nanoTime() - periodNanos;
                        }
                        if (!isCancelled()) {
                            // 重新加入隊(duì)列
                            scheduledExecutor().scheduledTaskQueue().add(this);
                        }
                    }
                }
            }
        } catch (Throwable cause) {
            setFailureInternal(cause);
        }
    }

當(dāng)任務(wù)的執(zhí)行時(shí)間還未到,則判斷任務(wù)是否已經(jīng)取消,如果已取消則移除任務(wù),否則重新加入隊(duì)列。對(duì)于只執(zhí)行一次的任務(wù),執(zhí)行完了不會(huì)再放回隊(duì)列。其他的任務(wù),則根據(jù)periodNanos 的類型,重新計(jì)算截止時(shí)間,重新放回隊(duì)列,等待下次調(diào)度。
定時(shí)任務(wù)的優(yōu)先級(jí)隊(duì)列到此介紹完畢,接著看NioEventLoop的run方法

                        nextWakeupNanos.set(curDeadlineNanos);
                        try {
                            // 再次判斷任務(wù)隊(duì)列中是否有任務(wù)
                            if (!hasTasks()) {
                                strategy = select(curDeadlineNanos);
                            }
                        }
    private int select(long deadlineNanos) throws IOException {
        // 如果沒有定時(shí)任務(wù),直接調(diào)Java NIO的select,進(jìn)入阻塞
        if (deadlineNanos == NONE) {
            return selector.select();
        }
        // 如果截止時(shí)間小于0.5ms,則timeoutMillis 為0,直接調(diào)非阻塞的selectNow()方法
        long timeoutMillis = deadlineToDelayNanos(deadlineNanos + 995000L) / 1000000L;
        return timeoutMillis <= 0 ? selector.selectNow() : selector.select(timeoutMillis);
    }

在調(diào)用select之前,再次調(diào)用hasTasks()判斷從上次調(diào)用該方法到目前為止是否有任務(wù)加入,多做了一層防護(hù),因?yàn)檎{(diào)用select時(shí),可能會(huì)阻塞,這時(shí),如果任務(wù)隊(duì)列中有任務(wù)就會(huì)長(zhǎng)時(shí)間得不到執(zhí)行,所以須小心謹(jǐn)慎。
如果任務(wù)隊(duì)列中還是沒有任務(wù),則會(huì)調(diào)用select方法。在這個(gè)方法中會(huì)根據(jù)入?yún)?code>deadlineNanos來選擇調(diào)用NIO的哪個(gè)select方法:

如果deadlineNanos為NONE,即沒有定時(shí)任務(wù)時(shí),直接調(diào)用NIO的無參select方法,進(jìn)入永久阻塞,除非檢測(cè)到Channel的IO事件或者被wakeup;
如果存在定時(shí)任務(wù),且定時(shí)任務(wù)的截止時(shí)間小于0.5ms,則timeoutMillis 為0,直接調(diào)非阻塞的selectNow方法,也就是說馬上有定時(shí)任務(wù)需要執(zhí)行了,不要再進(jìn)入阻塞了;
其他情況,調(diào)用select(timeout),進(jìn)入有超時(shí)時(shí)間的阻塞。

到這里,可能有人要問了:在上面的方法中,如果調(diào)用了Java NIO的無參的select方法,就會(huì)進(jìn)入阻塞,除非檢測(cè)到Channel的IO事件,那么在檢測(cè)到IO事件之前,加入到任務(wù)隊(duì)列中的任務(wù)怎么得到執(zhí)行呢?

好,你想,在檢測(cè)到IO事件之前,可以退出阻塞的方法是什么?對(duì),調(diào)用wakeup方法。那么我們來搜一下NioEventLoop中有調(diào)用Selector的wakeup方法的地方嗎:

    protected void wakeup(boolean inEventLoop) {
        if (!inEventLoop && nextWakeupNanos.getAndSet(AWAKE) != AWAKE) {
            selector.wakeup();
        }
    }

還真搜到了,再看一下這個(gè)方法被調(diào)用的地方

wakeup被調(diào)用.PNG

看到SingleThreadEventExecutor的execute方法了嗎,就是說在調(diào)execute方法,向EventLoop提交任務(wù)時(shí),會(huì)將EventLoop線程從Java NIO的select阻塞中喚醒。

    private void execute(Runnable task, boolean immediate) {
        boolean inEventLoop = inEventLoop();
        addTask(task);
        if (!inEventLoop) {
           ...
        }

        if (!addTaskWakesUp && immediate) {
            // 喚醒EventLoop線程,執(zhí)行任務(wù)隊(duì)列中的任務(wù)
            wakeup(inEventLoop);
        }
    }

到這里,NioEventLoop的run方法的職責(zé)之一:檢測(cè)Channel的IO事件就講解完畢。

至于IO事件的處理以及任務(wù)隊(duì)列中任務(wù)的處理會(huì)在后面的文章中解析,敬請(qǐng)期待。

總結(jié)

在本文中,對(duì)Netty的NioEventLoop進(jìn)行了深入的解讀,并且詳細(xì)講解了它的三大職責(zé)之一:檢測(cè)Channel的IO事件的機(jī)制。
NioEventLoop是Netty最核心的概念,內(nèi)部運(yùn)行機(jī)制很復(fù)雜,在接下來的兩篇文章中會(huì)繼續(xù)分析。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。