当前位置: 首页 > news >正文

高并发网络通信Netty之空轮询问题

一、问题背景

在 NioEventLoop 事件循环中,Selector 一次次 select() 返回为 0,且没有事件被触发,形成空转,导致 CPU 占用 100%,系统资源白白浪费。这种情况尤其在 高并发、连接数多IO事件少 的场景下更容易出现。

源码位置:NioEventLoop.java

Netty(基于 Java NIO)的底层用到了 Selector.select() 方法来阻塞等待事件。

private int select(long deadlineNanos) throws IOException {if (deadlineNanos == NONE) {return selector.select();}// Timeout will only be 0 if deadline is within 5 microsecslong timeoutMillis = deadlineToDelayNanos(deadlineNanos + 995000L) / 1000000L;return timeoutMillis <= 0 ? selector.selectNow() : selector.select(timeoutMillis);}

二、可能出现的原因

  • Linux内核(主要问题):在部分Linux的2.6的kernel中,poll和epoll对于突然中断的连接socket(如强制断网、防火墙中断连接会对返回的eventSet事件集合置为POLLHUP,也可能是POLLERR,eventSet事件集合发生了变化,这就可能导致Selector会被唤醒(select()/epoll_wait()立即返回0次事件);
  • JDKBug:尤其是 JDK 1.7~1.8 的 Selector 在 epoll 上的 bug:Selector.select() 会在 epoll 上不断空转;
  • 其他线程调用 Selector.wakeup()会唤醒正在阻塞的 select(),导致返回 0;
  • 注册的 Channel 被频繁地取消但未及时清理:导致 select 无法正确感知就绪事件;
  • 并发线程操作 Selector:多线程并发注册或唤醒 Selector 造成竞争问题。

三、危害

  • CPU 飙高(100%)

  • 线程空转

  • 延迟提升,任务堆积

四、Netty空轮询问题排查

1.找出进程中CPU高的线程

记录下 CPU 高的线程 ID(十进制)

top -Hp <pid>

转换为十六进制线程ID,用于分析

printf "%x\n" <线程ID>
2.使用 jstack 查看线程栈
jstack <pid> > jstack.txt

搜索高 CPU 线程对应的十六进制线程ID(如 0x57q),定位其线程栈:

grep -A 30 "nid=0x57q" jstack.txt
3.Netty空轮询的典型栈信息

如果是空轮询,堆栈信息一般会打印Netty以下这些类信息

sun.nio.ch.SelectorImpl.select
io.netty.channel.nio.NioEventLoop.select
io.netty.channel.nio.NioEventLoop.run

 或类似以下堆栈信息结构:

"nioEventLoopGroup-3-1" #35 daemon prio=5 os_prio=0 tid=0x00007fba2c002000 nid=0x57q runnable [0x00007fba18ffd000]java.lang.Thread.State: RUNNABLEat sun.nio.ch.SelectorImpl.select(Native Method)at io.netty.channel.nio.NioEventLoop.select(NioEventLoop.java:752)at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:420)at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:905)

注意:线程状态为 RUNNABLE,但没有执行业务逻辑,说明在空轮询。 

五、Netty官方解决方案

Netty 从 4.0.20.Final 之后 引入了自动修复机制

源码位置:NioEventLoop.java

 int selectorAutoRebuildThreshold = SystemPropertyUtil.getInt("io.netty.selectorAutoRebuildThreshold", 512);
SELECTOR_AUTO_REBUILD_THRESHOLD = selectorAutoRebuildThreshold;private boolean unexpectedSelectorWakeup(int selectCnt) {if (Thread.interrupted()) {if (logger.isDebugEnabled()) {logger.debug("Selector.select() returned prematurely because " +"Thread.currentThread().interrupt() was called. Use " +"NioEventLoop.shutdownGracefully() to shutdown the NioEventLoop.");}return true;}// 触发条件:连续 select() 返回 0 超过阈值(默认 512 次)if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {logger.warn("Selector.select() returned prematurely {} times in a row; rebuilding Selector {}.",selectCnt, selector);rebuildSelector();return true;}return false;}

连续 512 次空轮询(无事件)会触发一次 Selector 的重建。

// NioEventLoop.java
public void rebuildSelector() {// 1. 创建新Selectorfinal Selector newSelector = openSelector();// 2. 将旧Selector的Channel注册到新Selectorfor (SelectionKey key: oldSelector.keys()) {Channel ch = key.channel();// 重新注册Channel,并迁移监听事件ch.register(newSelector, key.interestOps(), key.attachment());}// 3. 替换旧Selectorselector = newSelector;// 4. 关闭旧Selector(延迟执行,避免阻塞)oldSelector.close();
}

六、实际项目中的优化建议

1.升级 JDK / Netty 版本
  • Netty 建议使用 4.1+ 版本

  • JDK 尽量使用 1.8u60+ 或 JDK11+,避免老版本 epoll 的空轮询 bug。

2.合理配置 Selector 空轮询重建阈值

默认值是 512 次,如果你希望更快响应,可配置为更小值(代价是频繁 rebuild 会影响性能),比如 64,观察 CPU 是否下降。

System.setProperty("io.netty.selectorAutoRebuildThreshold", "128");// 通过系统参数调整阈值(默认512)
-Dio.netty.selectorAutoRebuildThreshold=1024
3.避免不必要的 wakeup()调用
  • 如果业务线程调用 eventLoop.wakeup(),注意不要过于频繁。

  • 特别是 非 Netty 管理的线程调用时,要注意时机和频率。

七、总结

1.分层表述

  • 现象 → “Selector在无事件时被频繁唤醒,导致线程空转”;
  • 原因 → “JDK的epoll实现在连接异常中断时存在Bug”;
  • 解决方案 → “Netty通过计数空轮询次数,超过阈值后重建Selector”。

2.关联设计思想

  • “这是Fail-Safe机制的体现,通过自动重建避免单点故障”;

  • “类似Kafka处理ZooKeeper会话过期,都是通过重建恢复状态”

3.延伸扩展

  • “类似问题在Tomcat NIO中也有,需配置selectorTimeout参数”;

  • 强调操作原子性:重建过程需保证Channel事件丢失。

相关文章:

  • Cargo 与 Rust 项目
  • wx.getLocation线上版本无法弹出授权框?
  • httpclient实现http连接池
  • 深入理解JVM执行引擎
  • 湖北师范大学人工智能与计算机学院电子信息研究生课程《随机过程》第一次作业
  • go语言位运算
  • OneSug:快手发布了端到端Query Suggestion生成式模型,显著提升电商场景下的查询建议能力!!
  • FPGA基础 -- Verilog 共享任务(task)和函数(function)
  • 1.22Node.js 中操作 Redis
  • 信创 CDC 实战|国产数据库的数据高速通道:OceanBase 实时入仓 StarRocks
  • 408第二季 - 组成原理 - 指令的寻址方式
  • Linux 系统中,查询 JDK 的安装目录
  • uvicorn api:app --host 0.0.0.0 --port 7777容器运行失败
  • servlet前后端交互
  • TDengine 与开源可视化编程工具 Node-RED 集成
  • 使用 OpenCV 和传统机器学习实现工业开关状态识别
  • 智能群跃小助手发布说明
  • Happy-LLM-task3 :2.1 注意力机制 2 天
  • torchmd-net开源程序是训练神经网络潜力
  • 谷歌浏览器电脑版官方下载- Google Chrome官方网页版入口
  • 网站图片加载优化/可以进入任何网站的浏览器
  • php网站后台无法上传图片/微信公众号推广软文案例
  • 网站怎么在百度搜到/注册网站免费注册
  • 公司网站设计的费用/代运营竞价公司
  • 网站广告收入如何缴文化事业建设费/手机搜索引擎排行榜
  • 专做药材的网站有哪些/微信引流推广怎么做