Netty源码解析(四) —— NioEventLoop处理io

NioEventLoop处理io的一些操作方法


io.netty.channel.nio.NioEventLoop#run

    /**
     * 启动服务  轮询selector
     */
    @Override
    protected void run() {
        for (;;) {
            try {
                switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
                    case SelectStrategy.CONTINUE:
                        continue;
                    case SelectStrategy.SELECT:
                        // 重置 wakenUp 标记为 false
                        //wakenUp标识当前selector是否是唤醒状态
                        select(wakenUp.getAndSet(false));
                        if (wakenUp.get()) {
                            selector.wakeup();
                        }
                    default:
                }

                cancelledKeys = 0;
                needsToSelectAgain = false;
                final int ioRatio = this.ioRatio;
                if (ioRatio == 100) {
                    try {
                        //处理轮询到的key
                        processSelectedKeys();
                    } finally {
                        // Ensure we always run tasks.
                        //启动task
                        runAllTasks();
                    }
                } else {
                    final long ioStartTime = System.nanoTime();
                    try {
                        processSelectedKeys();
                    } finally {
                        // Ensure we always run tasks.
                        final long ioTime = System.nanoTime() - ioStartTime;
                        runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                    }
                }
            } catch (Throwable t) {
                ...
        }
    }

这个run方法是在NioEventLoop线程启动的时候调用的,这里面主要:

  1. 轮询准备好的key
  2. 处理轮训出来的key
  3. 运行task任务

io.netty.channel.nio.NioEventLoop#select

    /**
     * 轮询selector方法
     * @param oldWakenUp
     * @throws IOException
     */
    private void select(boolean oldWakenUp) throws IOException {
        Selector selector = this.selector;
        try {
            int selectCnt = 0;
            long currentTimeNanos = System.nanoTime();
            //delayNanos(currentTimeNanos)计算当前定时任务队列第一个任务的延迟时间
            //select的时间不能超过selectDeadLineNanos这个时间
            long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);

            for (;;) {
                long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
                if (timeoutMillis <= 0) {
                    //如果超时  并且第一次轮询
                    //那么就再进行一次非阻塞的select 然后break结束轮询
                    if (selectCnt == 0) {
                        selector.selectNow();
                        selectCnt = 1;
                    }
                    break;
                }

                //nioEventLoop有任务在进行,那么就进行一次非阻塞的select 然后break结束轮询
                if (hasTasks() && wakenUp.compareAndSet(false, true)) {
                    selector.selectNow();
                    selectCnt = 1;
                    break;
                }

                //定时任务为空  截止时间没到,进行阻塞select
                int selectedKeys = selector.select(timeoutMillis);
                selectCnt ++;

                if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {
                    // - Selected something,
                    // - waken up by user, or
                    // - the task queue has a pending task.
                    // - a scheduled task is ready for processing
                    break;
                }
                if (Thread.interrupted()) {
                    // Thread was interrupted so reset selected keys and break so we not run into a busy loop.
                    // As this is most likely a bug in the handler of the user or it's client library we will
                    // also log it.
                    //
                    // See https://github.com/netty/netty/issues/2426
                    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();
                /**
                 * time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos
                 * 属于正常的  证明我进行了一次阻塞select
                 * else :属于空轮训  就是进行了阻塞select但是立即返回了,并没有阻塞
                 */
                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) {
                    //当轮询次数超过一个阈值  512  要解决jdk的空轮训bug了
                    // The selector returned prematurely many times in a row.
                    // Rebuild the selector to work around the problem.
                    logger.warn(
                            "Selector.select() returned prematurely {} times in a row; rebuilding Selector {}.",
                            selectCnt, selector);

                    //重新创建一个selector  把oldSelector上的key赋值给他
                    rebuildSelector();
                    selector = this.selector;

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

                currentTimeNanos = time;
            }

           ...
    }

这个方法又一个jdk空轮训的方法,这个是nio原生就存在的bug,就是进行select操作立即返回,并没有阻塞式的select,因此for循环方法会一直走下去,这样就会导致cpu占用率很高,当轮询次数超过一个阈值 512 要解决jdk的空轮训bug了,下面我们来分析解决空轮训怎么实现

    /**
     * Replaces the current {@link Selector} of this event loop with newly created {@link Selector}s to work
     * around the infamous epoll 100% CPU bug.
     * 解决jdk空轮训导致cpu占用100%的bug
     * 方案:
     *      创建新的selector 把oldSelector上面的key都迁移到新的上面
     */
    public void rebuildSelector() {
        if (!inEventLoop()) {
            execute(new Runnable() {
                @Override
                public void run() {
                    rebuildSelector0();
                }
            });
            return;
        }
        rebuildSelector0();
    }

这里会判断当前执行的线程是否在NioEventLoop的线程中,如果不在NioEventLoop线程,则添加到task队列等待执行

    @Override
    public void execute(Runnable task) {
        if (task == null) {
            throw new NullPointerException("task");
        }
        //判断正在执行任务的线程是否是eventloop已经保存的线程
        boolean inEventLoop = inEventLoop();
        addTask(task);
        //如果不是在eventloop线程证明还没启动轮询和任务调度run方法
        //则启动线程
        if (!inEventLoop) {
            //启动线程
            startThread();
            if (isShutdown() && removeTask(task)) {
                reject();
            }
        }

        if (!addTaskWakesUp && wakesUpForTask(task)) {
            wakeup(inEventLoop);
        }
    }

这里判断不是在NioEventLoop线程的话就去启动线程,那么会每个不是在NioEventLoop线程执行的任务都去启动线程么?答案是否定的,且看startThread方法


    private void startThread() {
        //状态值是未启动过
        if (state == ST_NOT_STARTED) {
            if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
                try {
                    //真正启动的逻辑
                    doStartThread();
                } catch (Throwable cause) {
                    STATE_UPDATER.set(this, ST_NOT_STARTED);
                    PlatformDependent.throwException(cause);
                }
            }
        }
    }

下面我们来看真正启动线程的逻辑

    private void doStartThread() {
        assert thread == null;
        //交给线程执行器去执行代码 ThreadPerTaskExecutor
        executor.execute(new Runnable() {
            @Override
            public void run() {
                //保存当前线程异步任务的线程
                thread = Thread.currentThread();
                if (interrupted) {
                    thread.interrupt();
                }
                boolean success = false;
                updateLastExecutionTime();
                try {
                    //启动NioEventLoop的run()方法  进行selector轮询
                    SingleThreadEventExecutor.this.run();
                    success = true;
                } catch (Throwable t) {
                    logger.warn("Unexpected exception from an event executor: ", t);
                } finally {

                .....
    }

这里会把当前启动的线程保存到成员变量thread,后续判断是否是NioEventLoop线程也是比较线程是否是一个,然后调用NioEventLoop的run方法,所以run方法执行的代码都是在NioEventLoop线程里面运行的


回头再看io.netty.channel.nio.NioEventLoop#rebuildSelector0

  /**
     * 重新构建selector
     */
    private void rebuildSelector0() {
        //保存旧的selector
        final Selector oldSelector = selector;
        final SelectorTuple newSelectorTuple;

        if (oldSelector == null) {
            return;
        }

        try {
            //创建新的selector  创建过程也会走优化的流程
            newSelectorTuple = openSelector();
        } catch (Exception e) {
            logger.warn("Failed to create a new Selector.", e);
            return;
        }

        // 把所有的 channels 注册到新的 Selector.
        int nChannels = 0;
        for (SelectionKey key: oldSelector.keys()) {
            Object a = key.attachment();
            try {
                //key失效 跳过
                if (!key.isValid() || key.channel().keyFor(newSelectorTuple.unwrappedSelector) != null) {
                    continue;
                }

                //注册的关心事件
                int interestOps = key.interestOps();
                key.cancel();
                //取出来key里面的channel  注册到新的selector里面  返回新的selectionKey
                SelectionKey newKey = key.channel().register(newSelectorTuple.unwrappedSelector, interestOps, a);
                if (a instanceof AbstractNioChannel) {
                    // Update SelectionKey  如果attachment是AbstractNioChannel
                    //那么将AbstractNioChannel上的selectionKey也更新一下
                    ((AbstractNioChannel) a).selectionKey = newKey;
                }
                //channel的数量增加
                nChannels ++;
            } catch (Exception e) {
               ....
        }

        //把新的selector引用传递给成员变量
        selector = newSelectorTuple.selector;
        unwrappedSelector = newSelectorTuple.unwrappedSelector;

        try {
            // time to close the old selector as everything else is registered to the new one
            //关闭旧的selector
            oldSelector.close();
        } catch (Throwable t) {
            ....
        }
    }

这里主要就是为了解决空轮训的方法,创建一个新的selector,把oldSelector上面的key都注册到新的上面,这样就解决了

扫描二维码关注公众号,回复: 3167807 查看本文章

承接上文,解决完空轮训,也轮询除了key,那么接着就是处理key了
io.netty.channel.nio.NioEventLoop#processSelectedKeys

    /**
     * selectedKeys不为null证明我们用的是优化后的selector
     * 那么就按照优化后的逻辑去处理
     */
    private void processSelectedKeys() {
        if (selectedKeys != null) {
            processSelectedKeysOptimized();
        } else {
            processSelectedKeysPlain(selector.selectedKeys());
        }
    }

这里主要是对优化和未优化的selector分别做的处理,大致逻辑都一样,只不过因为底层selectKeys的数据结构不一样,遍历方法不一样,我们只分析processSelectedKeysOptimized

    /**
     * 处理优化后的selector,selectedKeys是一个数组
     */
    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
            //拿到selectedKey,把数组的引用置为null
            selectedKeys.keys[i] = null;

            //拿到key的attachment  也就是AbstractNioChannel
            final Object a = k.attachment();

            //如果是AbstractNioChannel
            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;
            }
        }
    }

NioEventLoop#processSelectedKey(java.nio.channels.SelectionKey,io.netty.channel.nio.AbstractNioChannel)

    /**
     * 处理select出来的key
     * @param k SelectionKey
     * @param ch AbstractNioChannel注册进去attachment
     */
    private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
        //拿到Unsafe对象 NioMessageUnsafe是服务端的channel操作对象
        //NioByteUnsafe 客户端的channel操作对象
        final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
        //key失效
        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 {
            //拿到key的事件标识
            int readyOps = k.readyOps();
            //连接事件
            if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
                int ops = k.interestOps();
                ops &= ~SelectionKey.OP_CONNECT;
                k.interestOps(ops);

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

            /**
             * 读事件和 accept事件都会经过这里,但是拿到的unsafe对象不同  所以后续执行的read操作也不一样
             * NioServerChannel进行accept操作
             * NioChannel进行read操作
             */
            if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
                unsafe.read();
            }
        } catch (CancelledKeyException ignored) {
            unsafe.close(unsafe.voidPromise());
        }
    }

我们分析当服务端channel accept到新的连接时候unsafe.read();走的是NioMessageUnsafe,当客户端连接read事件到来时候,走的是NioByteUnsafe.read()方法

    private final class NioMessageUnsafe extends AbstractNioUnsafe {

        private final List<Object> readBuf = new ArrayList<Object>();

        @Override
        public void read() {
            assert eventLoop().inEventLoop();
            final ChannelConfig config = config();
            //拿到channel处理的pipline
            final ChannelPipeline pipeline = pipeline();
            final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
            allocHandle.reset(config);

            boolean closed = false;
            Throwable exception = null;
            try {
                try {
                    do {
                        //readBuf是一个集合  来保存accept出的channel
                        //具体read操作
                        int localRead = doReadMessages(readBuf);
                        if (localRead == 0) {
                            break;
                        }
                        if (localRead < 0) {
                            closed = true;
                            break;
                        }

                        allocHandle.incMessagesRead(localRead);
                    } while (allocHandle.continueReading());
                } catch (Throwable t) {
                    exception = t;
                }

                int size = readBuf.size();
                for (int i = 0; i < size; i ++) {
                    readPending = false;
                    //调用pipline的read方法通知事件
                    pipeline.fireChannelRead(readBuf.get(i));
                }
                //清除readBuf集合
                readBuf.clear();
                allocHandle.readComplete();
                pipeline.fireChannelReadComplete();

               .....
            } finally {
              .....
            }
        }
    }
  1. 把accept到的新连接channel添加到集合
  2. 调用pipline的的通知事件pipeline.fireChannelRead(readBuf.get(i));
  3. 通知channel read完毕pipeline.fireChannelReadComplete();

接着我们看doReadMessages方法

  1. 服务端io.netty.channel.socket.nio.NioServerSocketChannel#doReadMessages处理的是accept到的channel,并且进行后续的注册到worker group中的NioEventLoop中去处理
  2. 客户端的NioSocketChannel#doReadMessages处理的是worker group selector 轮询到的read事件

    io.netty.channel.socket.nio.NioServerSocketChannel#doReadMessages

    @Override
    protected int doReadMessages(List<Object> buf) throws Exception {
        //拿到新连接的channel
        SocketChannel ch = SocketUtils.accept(javaChannel());

        try {
            if (ch != null) {
                //添加到集合中去
                buf.add(new NioSocketChannel(this, ch));
                return 1;
            }
        } catch (Throwable t) {
            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;
    }

猜你喜欢

转载自blog.csdn.net/u011702633/article/details/81980025
今日推荐