• Netty源码学习(三)NioEventLoop


    0. NioEventLoop简介

    NioEventLoop如同它的名字,它是一个无限循环(Loop),在循环中不断处理接收到的事件(Event)

    在Reactor模型中,NioEventLoop就是Worker的角色,关联于多个Channel,监听这些Channel上的read/write事件,一旦有事件发生,就做出相应的处理

    1. NioEventLoop类图

    继承关系可以说是相当复杂了,我们慢慢分析

    2.  NioEventLoop的构造方法

        NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
                     SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler) {
            super(parent, executor, false, DEFAULT_MAX_PENDING_TASKS, rejectedExecutionHandler);
            if (selectorProvider == null) {
                throw new NullPointerException("selectorProvider");
            }
            if (strategy == null) {
                throw new NullPointerException("selectStrategy");
            }
            provider = selectorProvider;
            final SelectorTuple selectorTuple = openSelector();
            selector = selectorTuple.selector;
            unwrappedSelector = selectorTuple.unwrappedSelector;
            selectStrategy = strategy;
        }
    
    
        private SelectorTuple openSelector() {
            final Selector unwrappedSelector;
            try {
                unwrappedSelector = provider.openSelector();//利用JDK提供的SelectorProvider直接创建一个Selector
            } catch (IOException e) {
                throw new ChannelException("failed to open a new selector", e);
            }
    
            if (DISABLE_KEYSET_OPTIMIZATION) {//如果没有开启KEYSET优化,将上面的那个Selector直接返回
                return new SelectorTuple(unwrappedSelector);
            }
    
            final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();//创建一个专门存放SelectionKey的Set对象
    
            Object maybeSelectorImplClass = AccessController.doPrivileged(new PrivilegedAction<Object>() {
                @Override
                public Object run() {
                    try {//用反射的方式创建一个SelectorImpl对象
                        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;
    
            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);
                        if (cause != null) {
                            return cause;
                        }
                        cause = ReflectionUtil.trySetAccessible(publicSelectedKeysField);
                        if (cause != null) {
                            return cause;
                        }
    
                        selectedKeysField.set(unwrappedSelector, selectedKeySet);//强制将SelectorImpl中的selectedKeys域替换为优化版的SelectedSelectionKeySet对象
                        publicSelectedKeysField.set(unwrappedSelector, selectedKeySet);//强制将SelectorImpl中的publicSelectedKeys域替换为优化版的SelectedSelectionKeySet对象
                        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));
        }

    构造方法的主要作用是创建了一个SelectorImpl对象,如果没有设置DISABLE_KEYSET_OPTIMIZATION属性,SelectorImpl中类型为Set<SelectionKey>的selectedKeys与publicSelectedKeys域会被替换为一个SelectedSelectionKeySet对象。

    为什么搞得这么麻烦呢?因为默认的域是Set类型,插入元素的开销是o(log n),而优化版的SelectedSelectionKeySet继承了AbstractSet,具有Set的功能,但是内部是用数组实现,只具有add功能,而且其开销为o(1)。

    3. NioEventLoop.run()

    run方法是NioEventLoop的核心,此方法会在无限循环中监听关联的Channel上是否有新事件产生

        @Override
        protected void run() {
            for (;;) {//无限循环
                try {
                    switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {//如果任务队列中有任务,调用selectNow()方法,如果没有,则直接返回SelectStrategy.SELECT
                        case SelectStrategy.CONTINUE://没搞懂这个分支的目的是什么,全局搜了一下SelectStrategy.CONTINUE,没发现有赋这个值的地方
                            continue;
                        case SelectStrategy.SELECT:
                            select(wakenUp.getAndSet(false));//调用select()方法,尝试从关联的channel里读取IO事件。需要注意的是这个select方法相当复杂,因为它悄悄的解决了老版本的JDK的select方法存在的bug,有兴趣的可以仔细分析一下相关源码
                            if (wakenUp.get()) {
                                selector.wakeup();
                            }
                        default:
                    }
    
                    cancelledKeys = 0;
                    needsToSelectAgain = false;
                    final int ioRatio = this.ioRatio;//ioRatio代表EventLoop会花多少时间在IO事件上
                    if (ioRatio == 100) {
                        try {
                            processSelectedKeys();//处理IO事件
                        } finally {
                            // Ensure we always run tasks.
                            runAllTasks();//处理CPU事件
                        }
                    } else {
                        final long ioStartTime = System.nanoTime();
                        try {
                            processSelectedKeys();//处理IO事件
                        } finally {
                            // Ensure we always run tasks.
                            final long ioTime = System.nanoTime() - ioStartTime;//本次循环处理IO事件的耗时
                            runAllTasks(ioTime * (100 - ioRatio) / ioRatio);//分给CPU事件的耗时
                        }
                    }
                } 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);
                }
            }
        }
    
    
        private void processSelectedKeys() {
            if (selectedKeys != null) {
                processSelectedKeysOptimized();//处理IO事件。由于这里采用的是优化版的SelectorImpl,IO事件已经被写在selectedKeys属性里了,所以无需额外传参
            } else {
                processSelectedKeysPlain(selector.selectedKeys());//未优化版的SelectorImpl,需要调用selectedKeys()方法才能获取准备好的IO事件
            }
        }
    
    
        private void processSelectedKeysOptimized() {
            for (int i = 0; i < selectedKeys.size; ++i) {//遍历已经准备好的IO事件
                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;//手动将数组元素赋为null,以帮助gc(因为在系统压力大的时候,SelectionKey数组靠后的部分会被占用,如果不手动将用过的元素设置为null,那么在系统压力小的时候,这些元素是不会被释放的,也就是内存泄漏了)
    
                final Object a = k.attachment();//附件是这个事件所关联的Channel,后续的代码会直接从这个Channel上读取数据
    
                if (a instanceof AbstractNioChannel) {
                    processSelectedKey(k, (AbstractNioChannel) a);//处理某个IO事件
                } 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) {
            final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
            if (!k.isValid()) {
                final EventLoop eventLoop;
                try {
                    eventLoop = ch.eventLoop();
                } catch (Throwable ignored) {
                    // If the channel implementation throws an exception because there is no event loop, we ignore this
                    // because we are only trying to determine if ch is registered to this event loop and thus has authority
                    // to close ch.
                    return;
                }
                // Only close ch if ch is still registered to this EventLoop. ch could have deregistered from the event loop
                // and thus the SelectionKey could be cancelled as part of the deregistration process, but the channel is
                // still healthy and should not be closed.
                // See https://github.com/netty/netty/issues/5125
                if (eventLoop != this || eventLoop == null) {
                    return;
                }
                // close the channel if the key is not valid anymore
                unsafe.close(unsafe.voidPromise());
                return;
            }
    
            try {
                int readyOps = k.readyOps();//获取事件的类型
                // We first need to call finishConnect() before try to trigger a read(...) or write(...) as otherwise
                // the NIO JDK channel implementation may throw a NotYetConnectedException.
                if ((readyOps & SelectionKey.OP_CONNECT) != 0) {//不太理解这段代码的原因
                    // remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking
                    // See https://github.com/netty/netty/issues/924
                    int ops = k.interestOps();
                    ops &= ~SelectionKey.OP_CONNECT;
                    k.interestOps(ops);
    
                    unsafe.finishConnect();
                }
    
                // Process OP_WRITE first as we may be able to write some queued buffers and so free memory.
                if ((readyOps & SelectionKey.OP_WRITE) != 0) {//如果是可写事件,则flush缓存
                    // 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()方法
                    unsafe.read();//此处的unsafe为NioSocketChannel.NioSocketChannelUnsafe
                }
            } catch (CancelledKeyException ignored) {
                unsafe.close(unsafe.voidPromise());
            }
        }

    代码很长,大概逻辑如下:

    a. 在无限循环中调用Selector.select方法

    b. 使用ioRatio控制IO事件与CPU事件的耗时比例(ioRatio的默认值为50)

    c. 如果有IO事件发生,遍历所有IO事件并调用processSelectedKey方法

    d. 在processSelectedKey中,如果发现事件是READ/ACCEPT类型,则调用NioSocketChannel.NioSocketChannelUnsafe.read()方法对事件进行处理。其实际实现位于AbstractNioByteChannel中:

            @Override
            public final void read() {
                final ChannelConfig config = config();
                final ChannelPipeline pipeline = pipeline();//获取用户注册的pipeline
                final ByteBufAllocator allocator = config.getAllocator();//默认值为PooledByteBufAllocator,也就是申请direct memory
                final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
                allocHandle.reset(config);
    
                ByteBuf byteBuf = null;
                boolean close = false;
                try {
                    do {
                        byteBuf = allocHandle.allocate(allocator);//开辟一块内存作为buffer
                        allocHandle.lastBytesRead(doReadBytes(byteBuf));//doReadBytes方法会从绑定的Channel里读取数据到buffer里。顺便记录一下本次读了多少字节的数据出来
                        if (allocHandle.lastBytesRead() <= 0) {
                            // nothing was read. release the buffer.
                            byteBuf.release();
                            byteBuf = null;
                            close = allocHandle.lastBytesRead() < 0;
                            break;
                        }
    
                        allocHandle.incMessagesRead(1);//计数
                        readPending = false;
                        pipeline.fireChannelRead(byteBuf);//触发pipeline的channelRead事件
                        byteBuf = null;
                    } while (allocHandle.continueReading());
    
                    allocHandle.readComplete();
                    pipeline.fireChannelReadComplete();//触发pipeline的channelReadComplete事件
    
                    if (close) {
                        closeOnRead(pipeline);
                    }
                } catch (Throwable t) {
                    handleReadException(pipeline, byteBuf, t, close, allocHandle);
                } finally {
                    // Check if there is a readPending which was not processed yet.
                    // This could be for two reasons:
                    // * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method
                    // * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method
                    //
                    // See https://github.com/netty/netty/issues/2254
                    if (!readPending && !config.isAutoRead()) {
                        removeReadOp();
                    }
                }
            }
        }

    这段代码主要做了两件事情:

    a. 在循环中将Channel可读的数据读到一个临时的ByteBuf中

    b. 调用pipeline.fireChannelRead方法,处理读取到数据。(具体的处理逻辑在后续文章中阐述)

    现在我们已经基本搞清楚NioEventLoop的工作逻辑了:

    在无限循环中监听绑定的Channel上的事件,如果有ACCEPT/READ事件发生,则从Channel里读取数据,并调用关联的pipeline的fireChannelRead方法进行处理。

    目前仍不清楚的地方是:NioEventLoop是如何与Channel/Pipeline绑定的?

    且听后文分解。

  • 相关阅读:
    转:浅谈图片服务器的架构演进
    转:Spring AOP详解
    转:Spring AOP 注解方式实现的一些“坑”
    spring AOP自定义注解方式实现日志管理
    spring的普通类中如何取session和request对像
    spring session
    转:通过Spring Session实现新一代的Session管理
    转:一个Restful Api的访问控制方法(简单版)
    Python : 什么是*args和**kwargs[转载]
    tornado项目注意点
  • 原文地址:https://www.cnblogs.com/stevenczp/p/7581980.html
Copyright © 2020-2023  润新知