欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java NIO epoll 空转问题 + Netty 解决方法

程序员文章站 2022-03-12 11:26:48
...

在 Java NIO 编程实践中,很多人都会选择 Netty 作为基础框架,而不是直接用 JDK 原生的 NIO API。
因为 JDK 原生的 NIO 框架内容过于繁杂、学习成本高、补齐可靠性的工作量和难度都很大、还有一些bug。
其中一个著名的bug就是 epoll Selector 空转问题。

 

相关Bug单

 

问题表象

示例代码(仅做示例,未考虑异常处理):

while (true) {
  selector.select();

  Iterator it = selector.selectedKeys().iterator();
  while (it.hasNext()) {
    SelectionKey key = (SelectionKey) it.next();
    int ops = key.interestOps();

    if (0 != (ops & SelectionKey.OP_ACCEPT)) {
      // 处理新连接
    }

    if (0 != (ops & SelectionKey.OP_READ)) {
      // 读取消息
    }

    it.remove();
  }
}

 

在使用 JDK NIO 框架时我们通常会采用上述模式的代码来处理客户端请求。
执行 Selector.select() 方法时会一直阻塞,直到有 channel 就绪。
但是在实践中,可能:

  1. 没有 channel 就绪,该方法也会返回。(违反原来的阻塞行为设计)
  2. 因为没有 channel 就绪,所以内部的 while 循环不会执行。
  3. 继而不断执行外部的 while (true) 循环。
  4. 上述步骤不断重复就形成空转轮询,CPU占用率达到100%,无法执行其它任务,最终程序崩溃。

 

问题原因

在部分 Linux 内核中,在 poll 或 epoll 一个已连接的socket,且请求事件掩码为0 的情况下,如果连接被突然中断,那么 poll/epoll 会被唤醒,相应事件标识为 POLLHUP(或 POLLERR)。
继而Selector被唤醒,且 interest set 为0,没有相应的 Channel,select() 返回值也是0。

JDK-6403933 写道
This is an issue with poll (and epoll) on Linux. If a file descriptor for a connected socket is polled with a request event mask of 0, and if the connection is abruptly terminated (RST) then the poll wakes up with the POLLHUP (and maybe POLLERR) bit set in the returned event set. The implication of this behaviour is that Selector will wakeup and as the interest set for the SocketChannel is 0 it means there aren't any selected events and the select method returns 0.

 

Netty 的解决方法

Netty 的解决方式是 重建一个新的 Selector,替代原来出错的 Selector
 

大致方法如下:

  • 在一个 select 周期中,统计 空select 操作 的次数
  • 当 空select 操作次数累计到阈值时,就认为触发了 epoll空转 bug。
  • 然后重建 Selector:
    新建一个 Selector;
    将 原Selector 上的 Channel 注册到 新Selector;
    关闭 原Selector。

上述判定阈值默认为 512。可通过JVM系统变量设置(io.netty.selectorAutoRebuildThreshold)。
可在 NioEventLoop 类中查看相关代码。


Java NIO epoll 空转问题 + Netty 解决方法
            
    
    博客分类: Java nettyepoll 

 

思考

可能因为此问题的根源在于底层Linux内核行为的不一致,所以Java官方一开始将其抛给了操作系统实现方,导致该bug存在了很久。也许Java官方相关决策者认为,此类bug最恰当的修复方案应该在底层操作系统,而不是由上层Java去糊一层,因为JDK有自己的设计原则,并不是无脑做得越多越好。
 

其实这种分层分治的思维方式是非常令人欣赏的。很多优秀产品的缔造者内心都秉持这个原则。
如,Robot Framework 的负责人 Pekka Klärck 认为 IronPython 处理全角空格的不同方式应该由 IronPython 实现者去修改,而不是让 Robot Framework 在上面糊一层:
Support for 'IDEOGRAPHIC SPACE' (U+3000) in test data on IronPython
 

反观很多产品经理,毫无原则,只会做附庸,客户说啥就是啥,领导说啥就是啥,自己也没有足够的知识和经验储备,尽出些拙劣的程序,根本不能称为产品。真的是“人人都是产品经理”。

 

  • Java NIO epoll 空转问题 + Netty 解决方法
            
    
    博客分类: Java nettyepoll 
  • 大小: 15 KB
相关标签: netty epoll