空轮询的产生

由于 JDK 自身的问题,再 NIO 操作的时候,可能会产生空轮询问题,当产生空轮询的时候,int selectedKeys selector.select() 并不会阻塞,会直接返回 0,并且会一直重复,导致服务不可用

Netty 判断是否是空轮询利用两个属性,一个时间

  • 两个属性
    • 一个是循环次数 selectCnt,每次循环 selectCnt 都会自增 1
    • 一个是阈值 SELECTOR_AUTO_REBUILD_THRESHOLD
  • 一个时间
    • 判断执行时间是否超过正常阻塞时间 timeoutMillis
  1. int selectorAutoRebuildThreshold = SystemPropertyUtil.getInt("io.netty.selectorAutoRebuildThreshold", 512);
  2. SELECTOR_AUTO_REBUILD_THRESHOLD = selectorAutoRebuildThreshold;

netty 默认的阈值 SELECTOR_AUTO_REBUILD_THRESHOLD = 512

发生空轮询

  • 判断执行时间超过等待执行时间的时候,表示没有发生空轮询
  • 如果等待时间小于当前时间,且 selectCnt >= 512 时,就有可能发生了空轮询
  1. for (; ; ) {
  2. // 省略部分代码...
  3. int selectedKeys = selector.select(timeoutMillis);
  4. // 轮询的次数,netty 认为 如果 selectCnt >= 512 就有可能发生了空轮询
  5. selectCnt++;
  6. // 省略部分代码...
  7. if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
  8. // timeoutMillis elapsed without anything selected.
  9. // 没有发生空轮询
  10. selectCnt = 1;
  11. } else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 && selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
  12. // The code exists in an extra method to ensure the method is not too big to inline as this
  13. // branch is not very likely to get hit very frequently.
  14. // 1、因为没有阻塞 timeoutMillis 的时间
  15. // 2、selectCnt >= 512
  16. // 就有可能发生了空轮询
  17. // ★★★ 重建 selector 对象
  18. selector = selectRebuildSelector(selectCnt);
  19. selectCnt = 1;
  20. break;
  21. }
  22. // 省略部分代码...
  23. }

规避思路

  • 拿到就的 selector 对象
  • 遍历 selector 对象中所有的 SelectionKey,拿到感兴趣的事件和附加对象
  • 取消 SelectionKey
  • 将 channel 注册到新的 selector 上,并注册上感兴趣的事件和附加对象
  • 返回新的 selector 对象
private void rebuildSelector0() {
    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;
    }

    // Register all channels to the new Selector.
    // ★ 将 oldSelector 上的有效 key 重新注册到 newSelectorTuple 上
    int nChannels = 0;
    for (SelectionKey key : oldSelector.keys()) {
        Object a = key.attachment();
        try {
            // 如果 key 已经无效,就继续寻找下一个 key
            if (!key.isValid() || key.channel().keyFor(newSelectorTuple.unwrappedSelector) != null) {
                continue;
            }
            // 获取感兴趣的事件
            int interestOps = key.interestOps();
            // 取消旧的 key
            key.cancel();
            // 注册到新的 newSelectorTuple 上
            SelectionKey newKey = key.channel().register(newSelectorTuple.unwrappedSelector, interestOps, a);
            if (a instanceof AbstractNioChannel) {
                // Update SelectionKey
                ((AbstractNioChannel) a).selectionKey = newKey;
            }
            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);
            }
        }
    }

    // ★ 赋值为新的 selector
    selector = newSelectorTuple.selector;
    unwrappedSelector = newSelectorTuple.unwrappedSelector;

    try {
        // time to close the old selector as everything else is registered to the new one
        // 关闭 oldSelector
        oldSelector.close();
    } catch (Throwable t) {
        if (logger.isWarnEnabled()) {
            logger.warn("Failed to close the old Selector.", t);
        }
    }

    if (logger.isInfoEnabled()) {
        logger.info("Migrated " + nChannels + " channel(s) to the new Selector.");
    }
}