Netty学习笔记通信调度篇:NioEventLoop源码解读

目录

初始化过程

Netty对Selecter的优化

注册channel过程

NioEventLoop的任务处理机制

run 方法实现

select

处理就绪事件

runAllTasks处理非I/O任务。


NioEventLoop 继承于 SingleThreadEventLoop, 而 SingleThreadEventLoop 又继承于 SingleThreadEventExecutor. SingleThreadEventExecutor 是 Netty 中对本地线程的抽象, 它内部有一个 Thread thread 属性, 存储了一个本地 Java 线程. 因此我们可以认为, 一个 NioEventLoop 其实和一个特定的线程绑定, 并且在其生命周期内, 绑定的线程都不会再改变。线程启动时会调用NioEventLoop的run方法,执行I/O任务和非I/O任务:

I/O任务
即selectionKey中ready的事件,如accept、connect、read、write等,由processSelectedKeys方法触发。

非IO任务
添加到taskQueue中的任务,如register0、bind0等任务,由runAllTasks方法触发。

两种任务的执行时间比由变量ioRatio控制,默认为50,则表示允许非IO任务执行的时间与IO任务的执行时间相等。

初始化过程

先来看一下他的构造方法,主要关注参数列表

  • NioEventLoopGroup 是管理NioEventLoop的集合
  • executor则为任务执行池
  • SelectorProvider 用于构造Selector对象
  • RejectedExecutionHandler用于在executor满了执行的逻辑
NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
             SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler) {
    super(parent, executor, false, DEFAULT_MAX_PENDING_TASKS, rejectedExecutionHandler); 
    provider = selectorProvider;//1
    final SelectorTuple selectorTuple = openSelector();//2
    selector = selectorTuple.selector;
    unwrappedSelector = selectorTuple.unwrappedSelector;
    selectStrategy = strategy;
}
  1. 在初始化NioEventLoopGroup的时候就已经用到了SelectorProvider这个参数了,SelectorProvider是Java NIO中的抽象类,它的作用是调用Windows或者Linux底层NIO的实现,为JavaNIO提供服务,比如经常用的Selector.open()方法内部就是通过调用SelectorProvider.openSelector()来得到多路复用器selector。在这里赋值给NioEventLoop的provider属性。
  2. SelectorTuple是NioEventLoop的内部类,其实就是对Java NIO Selector的封装。
    private static final class SelectorTuple {
        final Selector unwrappedSelector;
        final Selector selector;
    }
    

    selector和unwrappedSelector分别表示优化过的Selector和未优化过的Selector,selectedKeys表示优化过的SelectionKey。Netty在该类中对Java NIO的Selector做了优化,可以通过设置系统属性io.netty.noKeySetOptimization进行修改,设置为true、yes或者1关闭优化,设置为false、no或者0开启优化,默认开启优化,

接下里进入父类SingleThreadEventLoop的构造函数

protected SingleThreadEventLoop(EventLoopGroup parent, Executor executor,
                                boolean addTaskWakesUp, int maxPendingTasks,
                                RejectedExecutionHandler rejectedExecutionHandler) {
    super(parent, executor, addTaskWakesUp, maxPendingTasks, rejectedExecutionHandler);
    tailTasks = newTaskQueue(maxPendingTasks);
}

在此构造函数中只是初始化了TaskQueue,长度默认为DEFAULT_MAX_PENDING_TASKS,该常量定义于SingleThreadEventLoop类中,默认为16。继续看父类SingleThreadEventExecutor的构造函数:

 protected SingleThreadEventExecutor(EventExecutorGroup parent, Executor executor,
                                        boolean addTaskWakesUp, int maxPendingTasks,
                                        RejectedExecutionHandler rejectedHandler) {
        super(parent);
        this.addTaskWakesUp = addTaskWakesUp;
        this.maxPendingTasks = Math.max(16, maxPendingTasks);
        this.executor = ObjectUtil.checkNotNull(executor, "executor");
        taskQueue = newTaskQueue(this.maxPendingTasks);
        rejectedExecutionHandler = ObjectUtil.checkNotNull(rejectedHandler, "rejectedHandler");
    }

executor即线程池,之前在初始化NioEventLoopGroup的过程中,在MultithreadEventExecutorGroup构造函数中执行executor = new ThreadPerTaskExecutor(newDefaultThreadFactory()),一边保存在类属性中,一边传入了newChild方法中,最终也传入该构造函数。

并且在SingleThreadEventExecutor类中有一个属性private volatile Thread thread,它用来引用支撑该EventExecutor的线程,用来处理I/O事件和执行任务,叫支撑线程或者I/O线程均可,thread所引用的线程即来自executor。

这里也初始化了taskQueue,其中tailTasks和taskQueue均是任务队列,而优先级不同,taskQueue的优先级高于tailTasks和定时任务,定时任务优先级高于tailTasks。所谓的优先级就是线程执行任务的先后。只是tailTasksm目前在Netty中还没有用到。

newTaskQueuef方法被NioEventLoop重写,其实现是Mpsc队列(多个生产者单个消费者的意思)而在AbstractScheduledEventExecutor的scheduledTaskQueues是优先级队列。

Netty对Selecter的优化

在NioEventLoop实例化的过程中有提到,Netty对JDK Selector的优化,其实主要是对SelectKeys进行优化,JDK NIO中比如获取准备好的key通过如下代码:

 Set<SelectionKey> keys = selector.selectedKeys();

那么返回的是Set接口的实现HashSet,SeletctorImp中的定义如下。

protected Set<SelectionKey> selectedKeys = new HashSet();
protected HashSet<SelectionKey> keys = new HashSet();

每当向Selector注册时,对自动向key中添加元素,调用select方法后会更新该集合。Netty用SelectedSelectionKeySet实现了AbstractSet,提供了和HashSet同样的方法,只是内部实现(HashSet内部是HashMap)用数组来实现,至于为什么要这样做,本人认为主要以下亮点。

  1. 省去了Map中的value对象的内存,因为动辄百万连接的Netty产生了大量的SelectKey对象,value浪费的内存可想而知。
  2. 更方便的扩容。那么Netty是如何做到的?其实就是在NioEventLoop构造的过程中调用的openSelector方法内部。
private SelectorTuple openSelector() {
        final Selector unwrappedSelector;
        unwrappedSelector = provider.openSelector();
        if (DISABLE_KEYSET_OPTIMIZATION) {// 如果为开启key set 优化,构建普通的SelectorTuple
            return new SelectorTuple(unwrappedSelector);
        }
        // 获取到SelectorImpl的对象,可能失败
        Object maybeSelectorImplClass = AccessController.doPrivileged(new PrivilegedAction<Object>() {
            @Override
            public Object run() {
                try {
                    return Class.forName(
                            "sun.nio.ch.SelectorImpl",
                            false,
                            PlatformDependent.getSystemClassLoader());
                } catch (Throwable cause) {
                    return cause;
                }
            }
        });

        if (!(maybeSelectorImplClass instanceof Class) ||
            // ensure the current selector implementation is what we can instrument.
            !((Class<?>) maybeSelectorImplClass).isAssignableFrom(unwrappedSelector.getClass())) {
            if (maybeSelectorImplClass instanceof Throwable) {
                Throwable t = (Throwable) maybeSelectorImplClass;
                logger.trace("failed to instrument a special java.util.Set into: {}", unwrappedSelector, t);
            }
            return new SelectorTuple(unwrappedSelector);
        }

        final Class<?> selectorImplClass = (Class<?>) maybeSelectorImplClass;
        final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();
		// 获取到 SelectorImpl的属性 selectedKeys publicSelectedKeys
        Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
            @Override
            public Object run() {
                try {
                    Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
                    Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys");

                    if (PlatformDependent.javaVersion() >= 9 && PlatformDependent.hasUnsafe()) {
                        // Let us try to use sun.misc.Unsafe to replace the SelectionKeySet.
                        // This allows us to also do this in Java9+ without any extra flags.
                        long selectedKeysFieldOffset = PlatformDependent.objectFieldOffset(selectedKeysField);
                        long publicSelectedKeysFieldOffset =
                                PlatformDependent.objectFieldOffset(publicSelectedKeysField);

                        if (selectedKeysFieldOffset != -1 && publicSelectedKeysFieldOffset != -1) {
                            PlatformDependent.putObject(
                                    unwrappedSelector, selectedKeysFieldOffset, selectedKeySet);
                            PlatformDependent.putObject(
                                    unwrappedSelector, publicSelectedKeysFieldOffset, selectedKeySet);
                            return null;
                        }
                        // We could not retrieve the offset, lets try reflection as last-resort.
                    }

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

        if (maybeException instanceof Exception) {
            selectedKeys = null;
            Exception e = (Exception) maybeException;
            logger.trace("failed to instrument a special java.util.Set into: {}", unwrappedSelector, e);
            return new SelectorTuple(unwrappedSelector);
        }
        selectedKeys = selectedKeySet;
        logger.trace("instrumented a special java.util.Set into: {}", unwrappedSelector);
        return new SelectorTuple(unwrappedSelector,
                                 new SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet));
    }

先是通过相应平台的的Epoll实现,创建Selector对象,然后构造一个SelectedSelectionKeySet对象,这是Netty自己对SelectKeys的实现,然后通过反射将Selector对象中的selectedKeySet成员变量替换为自己的实现。此处应该全体起立喊666,哈哈!更绝的是Netty最新的4.x版本中加了一条:如果JDK版本大于等于9,连反射都不用了,直接通过CAS操作,通过成员变量的的偏移地址修改。

注册channel过程

channel关联Eventloop有三种情况:客户端SocketChannel关联EventLoop、服务端ServerSocketChannel关联EventLoop、由服务端ServerSocketChannel创建的SocketChannel关联EventLoop。Netty厉害的就是把这三种情况都都能复用Multithread EventLoopGroup中的register方法:

@Override
public ChannelFuture register(Channel channel) {
    return next().register(channel);
}

根据选择策略找到可用的EventLoop,然后调用SingleThreadEventLoop中的register方法,最终调用了 AbstractChannel#AbstractUnsafe.register 后


public final void register(EventLoop eventLoop, final ChannelPromise promise) {
    // 删除条件检查.
    AbstractChannel.this.eventLoop = eventLoop;
    if (eventLoop.inEventLoop()) {
        register0(promise);
    } else {
        try {
            eventLoop.execute(new OneTimeTask() {
                @Override
                public void run() {
                    register0(promise);
                }
            });
        } catch (Throwable t) {
            ...
        }
    }
}

将一个 EventLoop 赋值给 AbstractChannel 内部的 eventLoop 字段, 到这里就完成了 EventLoop 与 Channel 的关联过程。
但是上面代码一直是bind过程,也及时说在main线程中执行,所以会跳入else分支。将注册操作包装为一个Runnable,提交给eventloop的execute方法,该方法实现是在SingleThreadEventExecutor中实现的。

NioEventLoop的任务处理机制

Java NIO流程:

通过 Selector.open() 打开一个 Selector.

将 Channel 注册到 Selector 中, 并设置需要监听的事件(interest set)

不断重复:

  • 调用 select() 方法
  • 调用 selector.selectedKeys() 获取 selected keys
  • 迭代每个 selected key:
  • 从 selected key 中获取 对应的 Channel 和附加信息(如果有的话)
  • 判断是哪些 IO 事件已经就绪了, 然后处理它们. 如果是 OP_ACCEPT 事件, 则调用 “SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept()” 获取 SocketChannel, 并将它设置为 非阻塞的, 然后将这个 Channel 注册到 Selector 中.
  • 根据需要更改 selected key 的监听事件.
  • 将已经处理过的 key 从 selected keys 集合中删除

run 方法实现

    @Override
    protected void run() {
//这里是个死循环,里面的run方法逻辑会一直执行
        for (;;) {
            try {
//根据是队列里是否有任务执行不同的策略
                switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
                    case SelectStrategy.CONTINUE:
                        continue;
                    case SelectStrategy.SELECT:
//select内部具体的方法看下面的源码
                        select(wakenUp.getAndSet(false));
                        if (wakenUp.get()) {
                            selector.wakeup();
                        }
                    default:
                }

                cancelledKeys = 0;
                needsToSelectAgain = false;
//这里是I/O与executor里任务执行的比率,如果设为100则表示I/O的传先级最高
                final int ioRatio = this.ioRatio;
                if (ioRatio == 100) {
                    try {
//处理网络I/O事件processSelectedKeys可以看下面的源码
                        processSelectedKeys();
                    } finally {
                      //最终不是会执行任务队列里的任务的
                        runAllTasks();
                    }
                } else {
                    final long ioStartTime = System.nanoTime();
                    try {
                        processSelectedKeys();
                    } finally {
                        //传入一个执行了io的时间,根据io执行时间算出任务能够执行多长时间
                        final long ioTime = System.nanoTime() - ioStartTime;
                      // runAllTasks看下面的源码
                        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);
            }
        }
    }

通过上面的run方法分析,内部有比较重要的三个方法, 我们下面来一一分析,其中比较重要的是select方法。里面最重要的逻辑便是判断执行selector的selectNow方法还是执行select方法。其中selectNow方法是非阻塞的,则select方法是阻塞的。hasTasks()方法判断当前taskQueue是否有元素。如果taskQueue中有元素,执行 selectNow() 方法,最终执行selector.selectNow(),该方法会立即返回。

select

void selectNow() throws IOException {
    try {
        selector.selectNow();
    } finally {
        // restore wakup state if needed
        if (wakenUp.get()) {
            selector.wakeup();
        }
    }
}

这个方法解决了Nio中臭名昭著的bug:selector的select方法导致cpu100%。解决思想是发现执行了512次select还是没有I/O事件,刚通过新建一个新的selector。具体代码如下(关键代码有相应的注释,如果taskQueue没有元素,执行 select(oldWakenUp) 方法,代码如下:

//这里处理的是select逻辑
    private void select(boolean oldWakenUp) throws IOException {
//这个Selector就 是 java.nio里的selector啦
        Selector selector = this.selector;
        try {
            int selectCnt = 0;
            long currentTimeNanos = System.nanoTime();
//selectDeadLineNanos得到的是最早任务的的一个执行时间
            long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);

            for (;;) {
//如果最早需要执行的任务时间在0.5秒内,则执行selector的sellectNow方法,selectNow方法是非阻塞的
                long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
                if (timeoutMillis <= 0) {
                    if (selectCnt == 0) {
                        selector.selectNow();
                        selectCnt = 1;
                    }
                    break;
                }
//再次确认有任务则执行selectNow方法
                if (hasTasks() && wakenUp.compareAndSet(false, true)) {
                    selector.selectNow();
                    selectCnt = 1;
                    break;
                }
 //如果没有队列里没有任务需要执行,则通过select阻塞方法,得到selectedKeys
                int selectedKeys = selector.select(timeoutMillis);
                selectCnt ++;
//selectedKeys不为空,则说明有相应的I/O事件,跳出循环,的run方法里会处理具体的I/O事件
                if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {
                    break;
                }
                if (Thread.interrupted()) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Selector.select() returned prematurely because " +
                                "Thread.currentThread().interrupt() was called. Use " +
                                "NioEventLoop.shutdownGracefully() to shutdown the NioEventLoop.");
                    }
                    selectCnt = 1;
                    break;
                }

                long time = System.nanoTime();
//如果最早任务的执行时间还没有相应的I/O事件,则把selectCnt置为1,重新开始内部的for循环
                if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
                    // timeoutMillis elapsed without anything selected.
                    selectCnt = 1;
                } else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&
                        selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
//下面的逻辑用于处理java selector空轮询的bug
                    logger.warn(
                            "Selector.select() returned prematurely {} times in a row; rebuilding Selector {}.",
                            selectCnt, selector);

                    rebuildSelector();
                    selector = this.selector;

                    // Select again to populate selectedKeys.
                    selector.selectNow();
                    selectCnt = 1;
                    break;
                }

                currentTimeNanos = time;
            }

            if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Selector.select() returned prematurely {} times in a row for Selector {}.",
                            selectCnt - 1, selector);
                }
            }
        } catch (CancelledKeyException e) {
            if (logger.isDebugEnabled()) {
                logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector {} - JDK bug?",
                        selector, e);
            }
            // Harmless exception - log anyway
        }
    }
  • delayNanos(currentTimeNanos):计算延迟任务队列中第一个任务的到期执行时间(即最晚还能延迟多长时间执行),默认返回1s。每个SingleThreadEventExecutor都持有一个延迟执行任务的优先队列PriorityQueue,启动线程时,往队列中加入一个任务。
  • 如果延迟任务队列中第一个任务的最晚还能延迟执行的时间小于500000纳秒,且selectCnt == 0(selectCnt 用来记录selector.select方法的执行次数和标识是否执行过selector.selectNow()),则执行selector.selectNow()方法并立即返回。
  • 否则执行selector.select(timeoutMillis),这个方法已经在深入浅出NIO Socket分析过。
  • 如果已经存在ready的selectionKey,或者selector被唤醒,或者taskQueue不为空,或则scheduledTaskQueue不为空,则退出循环。
  • 如果 selectCnt 没达到阈值SELECTOR_AUTO_REBUILD_THRESHOLD(默认512),则继续进行for循环。其中 currentTimeNanos 在select操作之后会重新赋值当前时间,如果selector.select(timeoutMillis)行为真的阻塞了timeoutMillis,第二次的timeoutMillis肯定等于0,此时selectCnt 为1,所以会直接退出for循环。
  • 如果触发了epool cpu100%的bug,selector.select(timeoutMillis)操作会立即返回,不会阻塞timeoutMillis,导致 currentTimeNanos 几乎不变,这种情况下,会反复执行selector.select(timeoutMillis),变量selectCnt 会逐渐变大,当selectCnt 达到阈值,则执行rebuildSelector方法,进行selector重建,解决cpu占用100%的bug。
public void rebuildSelector() {  
        if (!inEventLoop()) {  
            execute(new Runnable() {  
                @Override  
                public void run() {  
                    rebuildSelector();  
                }  
            });  
            return;  
        }  
        final Selector oldSelector = selector;  
        final Selector newSelector;  
        if (oldSelector == null) {  
            return;  
        }  
        try {  
            newSelector = openSelector();  
        } catch (Exception e) {  
            logger.warn("Failed to create a new Selector.", e);  
            return;  
        }  
        // Register all channels to the new Selector.  
        int nChannels = 0;  
        for (;;) {  
            try {  
                for (SelectionKey key: oldSelector.keys()) {  
                    Object a = key.attachment();  
                    try {  
                        if (key.channel().keyFor(newSelector) != null) {  
                            continue;  
                        }  
                        int interestOps = key.interestOps();  
                        key.cancel();  
                        key.channel().register(newSelector, interestOps, a);  
                        nChannels ++;  
                    } catch (Exception e) {  
                        logger.warn("Failed to re-register a Channel to the new Selector.", e);  
                        if (a instanceof AbstractNioChannel) {  
                            AbstractNioChannel ch = (AbstractNioChannel) a;  
                            ch.unsafe().close(ch.unsafe().voidPromise());  
                        } else {  
                            @SuppressWarnings("unchecked")  
                            NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;  
                            invokeChannelUnregistered(task, key, e);  
                        }  
                    }  
                }  
            } catch (ConcurrentModificationException e) {  
                // Probably due to concurrent modification of the key set.  
                continue;  
            }  
  
            break;  
        }    
        selector = newSelector;  
        try {  
            // time to close the old selector as everything else is registered to the new one  
            oldSelector.close();  
        } catch (Throwable t) {  
            if (logger.isWarnEnabled()) {  
                logger.warn("Failed to close the old Selector.", t);  
            }  
        }    
        logger.info("Migrated " + nChannels + " channel(s) to the new Selector.");  
    }  

rebuildSelector过程:

  1. 通过方法openSelector创建一个新的selector。
  2. 将old selector的selectionKey执行cancel。
  3. 将old selector的channel重新注册到新的selector中。

对selector进行rebuild后,需要重新执行方法selectNow,检查是否有已ready的selectionKey。

方法selectNow()或select(oldWakenUp)返回后,执行方法processSelectedKeys和runAllTasks。

处理就绪事件

processSelectedKeys 用来处理有事件发生的selectkey,

//根据selectedKeys 不同,采用不同的方法处理
    private void processSelectedKeys() {
        if (selectedKeys != null) {
            processSelectedKeysOptimized();
        } else {
//调用selector.selectedKeys()方法得到selectedKeys
            processSelectedKeysPlain(selector.selectedKeys());
        }
    }

//这里处理的是没有优化过的SelectedKeys
    private void processSelectedKeysPlain(Set<SelectionKey> selectedKeys) {
        if (selectedKeys.isEmpty()) {
            return;
        }
//循环处理SelectionKey
        Iterator<SelectionKey> i = selectedKeys.iterator();
        for (;;) {
            final SelectionKey k = i.next();
            final Object a = k.attachment();
            i.remove();

            if (a instanceof AbstractNioChannel) {
//具体的I/O事件在processSelectedKey方法里
                processSelectedKey(k, (AbstractNioChannel) a);
            } else {
                @SuppressWarnings("unchecked")
                NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
                processSelectedKey(k, task);
            }

            if (!i.hasNext()) {
                break;
            }

            if (needsToSelectAgain) {
                selectAgain();
                selectedKeys = selector.selectedKeys();

                // Create the iterator again to avoid ConcurrentModificationException
                if (selectedKeys.isEmpty()) {
                    break;
                } else {
                    i = selectedKeys.iterator();
                }
            }
        }
    }


    private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
        final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
        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();
//根据 readyOps 判断I/O事件
            if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
//处理connect事件,
                int ops = k.interestOps();
                ops &= ~SelectionKey.OP_CONNECT;
                k.interestOps(ops);

                unsafe.finishConnect();
            }

            if ((readyOps & SelectionKey.OP_WRITE) != 0) {
              //处理write事件
                ch.unsafe().forceFlush();
            }

            if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
//处理read与accept事件
                unsafe.read();
            }
        } catch (CancelledKeyException ignored) {
            unsafe.close(unsafe.voidPromise());
        }
    }

判断 selectedKeys 这个变量,这个变量是一个 Set 类型,但 Netty 内部使用了 SelectionKey 类型的数组,而不是 Set实现,当想selector注册channel时,JDK 的 NIO 会向这个 set 添加 SelectionKey。当 selector 方法有返回值的时候,JDK NIO会Update 该集合。在Selector源码分析中有讲到。通过上面的代码我们看到,如果不是 null(默认开启优化) ,使用优化过的 SelectionKeys,也就是数组,如果没有开启优化,则使用 JDK 默认的。这里对优化过的方法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 {
                @SuppressWarnings("unchecked")
                NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
                processSelectedKey(k, task);
            }
            if (needsToSelectAgain) {
                selectedKeys.reset(i + 1);
                selectAgain();
                i = -1;
            }
        }
    }

其实就俩个步骤迭代 selectedKeys 获取就绪的 IO 事件, 然后为每个事件都调用 processSelectedKey 来处理它。这也正是JavaNIO流程的对应步骤。

runAllTasks处理非I/O任务。

如果 ioRatio 不为100时,方法runAllTasks的执行时间只能为ioTime * (100 - ioRatio) / ioRatio,其中ioTime 是方法processSelectedKeys的执行时间,在超过这个时间后会退出执行,以使线程能够执行I/O逻辑。

    protected boolean runAllTasks(long timeoutNanos) {
        fetchFromScheduledTaskQueue();
//从队列里取出执需要执行的task
        Runnable task = pollTask();
        if (task == null) {
            afterRunningAllTasks();
            return false;
        }
//这个方法求出执行的最后时间
        final long deadline = ScheduledFutureTask.nanoTime() + timeoutNanos;
        long runTasks = 0;
        long lastExecutionTime;
        for (;;) {
//执行任务
            safeExecute(task);

            runTasks ++;

//只有执行了64个任务才比较当前时间与deadline的大小,如果超过了则直接退出,这样做的原因是取得系统的nanoTime也是个相对耗时的操作
            if ((runTasks & 0x3F) == 0) {
                lastExecutionTime = ScheduledFutureTask.nanoTime();
                if (lastExecutionTime >= deadline) {
                    break;
                }
            }
//继续从队列里拿出未执行的task
            task = pollTask();
            if (task == null) {
                lastExecutionTime = ScheduledFutureTask.nanoTime();
                break;
            }
        }

        afterRunningAllTasks();
        this.lastExecutionTime = lastExecutionTime;
        return true;
    }

方法fetchFromScheduledTaskQueue把scheduledTaskQueue中已经超过延迟执行时间的任务移到taskQueue中等待被执行。

private void fetchFromScheduledTaskQueue() {
    if (hasScheduledTasks()) {
        long nanoTime = AbstractScheduledEventExecutor.nanoTime();
        for (;;) {
            Runnable scheduledTask = pollScheduledTask(nanoTime);
            if (scheduledTask == null) {
                break;
            }
            taskQueue.add(scheduledTask);
        }
    }
}

依次从taskQueue任务task执行,每执行64个任务,进行耗时检查,如果已执行时间超过预先设定的执行时间,则停止执行非IO任务,避免非IO任务太多,影响IO任务的执行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值