发布网友 发布时间:2024-10-19 18:09
共1个回答
热心网友 时间:2024-11-30 10:51
我们线上存在一个 Dubbo 服务,遇到大量 CLOSE_WAIT 状态的连接,始终无法消失,因此进行了原因分析。
CLOSE_WAIT 状态出现在被动关闭方,收到对端 FIN 包后回复 ACK,但未发送 FIN 包之前。问题在于服务没有回复 FIN,原因可能是收到了 FIN 包却未发送响应,通过抓包验证了这一情况。
问题核心在于为什么没有回复 FIN。Dubbo 服务底层使用 Netty,作为普通的 TCP 服务端,关键在于 FIN 包的回复。
分析显示,如果服务没有发送 FIN 包,可能原因有:
1. 半连接队列或全连接队列积压,通过 ss 命令查看全连接队列大小和等待 accept 的连接个数。
2. LISTEN 状态的 socket,Recv-Q 表示等待用户进程 accept 的连接个数,Send-Q 表示全连接队列最大容纳的连接数。
非 LISTEN 状态的 socket,Recv-Q 表示 receive queue 字节大小,Send-Q 表示 send queue 字节大小。
通过 ss 命令确认 Recv-Q 为 0,全连接队列无积压。
嫌疑指向 Netty 没有注册事件,导致收到 FIN 包后无动于衷。
进一步发现,凌晨 1 点业务实例加载大量数据导致堆内存占满,持续进行 fullgc。Netty 线程出现 OOM 异常。在 org.jboss.netty.channel.socket.nio.NioServerBoss#process 方法中,Netty 调用 accept 取走连接,第 19 行尝试注册事件时抛出 java.lang.OutOfMemoryError 异常。
因此,Netty 处理不健壮,try-catch 包裹了 accept 连接和注册事件逻辑,在 OOM 异常处理时,未能成功注册事件或关闭连接,导致连接存在但不被监听处理。
推荐相关视频学习:
为模拟问题复现,可使用字节码注入或直接重构 Netty 源码。本地拥有 Netty 源码,采用重构方法更快。重新构建项目后,使用 nc 模拟健康检查握手并断开连接,CLOSE_WAIT 状态连接持续存在直至 Netty 进程退出。再次 nc 断开连接,新增 CLOSE_WAIT 状态。由于服务持续进行健康检查,导致 OOM 期间 CLOSE_WAIT 状态不断增加。
问题核心:Netty 代码不够健壮,尝试捕获异常时,未能正确处理连接注册事件或关闭连接,导致连接存在且未被监听。
修改方式:在 catch 处理 throwable 时关闭连接即可,最新版本的 Netty 代码这部分逻辑已优化,将 accept 和注册事件拆分。有兴趣的读者可以尝试。
学习 TCP、网络编程是解决类似问题的关键。