高并发网络通信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事件丢失。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/471.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Nginx+Tomcat负载均衡群集

一、NginxTomcat 负载均衡、动静分离 1、Tomcat 简介 名称由来&#xff1a;Tomcat 最初由 Sun 的软件构架师詹姆斯・邓肯・戴维森开发&#xff0c;后变为开源项目并由 Sun 贡献给 Apache 软件基金会。因 O’Reilly 开源项目常以动物命名相关书籍&#xff0c;他希望动物能自我照…

Linux下nginx访问路径页面

第一步&#xff1a;通过Xshell在虚拟机中下载nginx sudo apt-get install nginx 第二步&#xff1a;进入nginx配置页面 cd /etc/nginx 我这里创建了一个html文件夹 在进入去创建页面并且重新加载 boahuboahu-VMware-Virtual-Platform:/$ cd /etc/nginx boahuboahu-VMware-Vir…

python实战项目76:51job数据采集与分析

python实战项目76:51job数据采集与分析 一、数据采集二、数据预处理2.1 导入相关库、读取数据2.2 查看数据2.3 处理数据、删除重复值、删除空值2.4 处理薪资水平字段数据三、数据可视化3.1 不同公司规模招聘岗位数量分布3.2 不同公司性质招聘岗位数量分布3.3 不同年限要求招聘岗…

OPENGLPG第九版学习 - 纹理与帧缓存 part1

文章目录 6.1 纹理综述6.2 基木纹理类型6.3 创建并初始化纹理代理纹理 6.4 指定纹理数据6.4.1 显式设置纹理数据将静态数据载入到纹理对象 6.4.2 从缓存(目标对象GL_PIXEL_UNPACK_BUFFER)中加载纹理6.4.3 从文件加载图像(DDS为例)读取一个图像文件并返回内存中的纹素数据将纹素…

Redis 持久化机制详解:RDB、AOF 原理与面试最佳实践(AOF篇)

在上一章我们深入学习了 Redis 中重要的数据持久化机制 ——RDB&#xff08;Redis Database&#xff09;&#xff0c;了解了其通过周期性快照将数据以二进制文件形式保存到磁盘的原理&#xff0c;包括触发条件、文件结构以及优缺点等核心内容。 Redis 持久化机制详解&#xff…

NumPy数组操作详解

在现代数据科学与工程计算领域&#xff0c;高效的数组操作是实现复杂算法的基础。NumPy作为Python的核心科学计算库&#xff0c;提供了一套强大的多维数组对象及操作机制。本文深入探讨NumPy数组的各种操作&#xff0c;旨在帮助读者全面掌握其功能与应用场景。 数组创建与属性查…

ER图:数据库设计的可视化语言 - 搞懂数据关系的基石

在数据库设计和数据建模领域&#xff0c;ER图&#xff08;实体-关系图&#xff09; 绝对是最基础、最核心的可视化工具之一。它用最直观的方式描绘了现实世界中的数据及其关系&#xff0c;是构建可靠数据库的蓝图。今天&#xff0c;我们就来聊聊这个技术基石。 本文来自「大千A…

图像特征检测算法ORB

ORB&#xff08;Oriented FAST and Rotated BRIEF&#xff09;是一种在计算机视觉领域广泛应用的特征检测与描述算法。 算法原理 特征点检测 &#xff1a;ORB 算法结合了 FAST&#xff08;Features from Accelerated Segment Test&#xff09;特征点检测方法和 Harris 特征点检…

docker 目录更改,必须做数据迁移才能启动

要修改 Docker 镜像的存储位置 并迁移数据&#xff08;如从 /var/lib/docker 迁移到 /mnt/data/docker&#xff09;&#xff0c;需要以下步骤&#xff1a; 1. 停止 Docker 服务 在修改配置和迁移数据前&#xff0c;先停止 Docker 服务&#xff1a; sudo systemctl stop docke…

springboot入门之路(一)

文章目录 1.Spring Boot介绍2.开发你的第一个Spring Boot应用2.1创建POM2.2yml和properties的配置区别yml配置properties配置 2.3springbootDemo代码2.4创建一个可执行jar 参考文档&#xff1a;SpringBoot参考指南&#xff1b;SpringBoot实战 1.Spring Boot介绍 Spring Boot使…

设计模式之责任链模式

责任链模式是一种经典的设计模式&#xff0c;属于行为型设计模式&#xff0c;他的核心思想是&#xff1a;将请求的发起者与接受者进行解耦&#xff0c;让多个对象都有机会处理请求&#xff0c;从而避免了请求发送者与接受者之间的耦合关系。这些对象连接形成一条链&#xff0c;…

使用YOLO模型进行线程安全推理

概述 在多线程环境中运行YOLO 模型时需要特别注意线程安全问题。Python threading 模块允许同时运行多个线程&#xff0c;但在这些线程中使用YOLO 模型时&#xff0c;需要注意一些重要的安全问题。 Python 线程是一种并行计算形式&#xff0c;允许程序同时运行多个操作。不过…