2024-04-04  阅读(3)
版权声明:本文为博主付费文章,严禁任何形式的转载和摘抄,维权必究。 本文链接:https://www.skjava.com/mianshi/baodian/detail/1814061084

回答

Netty 是基于 Java NIO 的,其内部使用了 Java NIO 的 Selector 来实现非阻塞 IO。在 Java NIO 中,Selector 是基于操作系统的多路复用技术实现的,在某些情况下操作系统底层的多路复用机制可能会错误地通知 JVM,表明有 IO 事件准备就绪,而实际上并没有任何事件,导致 Java NIO 会不断地轮询检查,却始终没有事件处理,从而导致 "Selector 空轮询"。

Netty 为了解决这个 bug,采取了两个步骤:

  1. 检测 epoll bug:Netty 使用空轮训计数器 selectCnt 来记录空轮询的次数。在每次调用 select() 后,如果返回的就绪事件数量为 0,则表示发生了一次空轮询,则计数器 selectCnt + 1。如果该计数器到达设定的阈值(默认512),则 Netty 认为确实发生了空轮训问题。
  2. 解决 epoll bug:Netty 采用重建 Selector 的方式来解决这个问题。当 Netty 认为当前 Selector 已经可能发生了空轮询时,Netty 会创建一个新的 Selector 来替代这个出了问题的旧 Selector。然后将所有在旧 Selector 上注册的所有 Channel 注销,并将他们重新注册到新创建的 Selector 上,最后关闭旧的 Selector 释放资源。

详解

Netty 通过一种很巧妙的方式来解决了该 bug,解决该 bug 主要分为两步:

  1. 检测 epoll bug。
  2. 解决 epoll bug。

下面来看看 Netty 是通过哪种巧妙的方式来解决 epoll bug 的。

NioEventLoop#run()

protected void run() {
        int selectCnt = 0;
        for (;;) {
            try {
                // ...

                selectCnt++;
                
                // ...

                if (ranTasks || strategy > 0) {
                    if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS && logger.isDebugEnabled()) {
                        logger.debug("Selector.select() returned prematurely {} times in a row for Selector {}.",
                                selectCnt - 1, selector);
                    }
                    selectCnt = 0;
                } else if (unexpectedSelectorWakeup(selectCnt)) { // Unexpected wakeup (unusual case)
                    selectCnt = 0;
                }
            } catch (CancelledKeyException e) {
                // ...
            } catch (Error e) {
                throw e;
            } catch (Throwable t) {
                handleLoopException(t);
            } finally {
               // ...
            }
        }
    }