我开发了一个 Web 系统,有一个功能需要提供一个 HTTP REST 接口供设备端调用,我的 Web 用 Java 开发,设备的用的是 Labview ,他的代码具体怎么实现我不清楚。
系统在运行一段时间后,客户反馈 HTTP 接口偶发调用失败,但是我通过查询日志,发现在上报接口无法调用的时刻,后台日志中接口都没有被触发。这个时候想到了看一下 TCP 连接数,监控发现 Web 系统有很多 CLOSE_WAIT 的 TCP 连接。
netstat -an | grep "8080" | grep 'CLOSE_WAIT'
后面发现一个规律,随着 CLOSE_WAIT 连接越来越多,各个设备反馈接口无法调用的频率也随之增多。
经过一番折腾,我考虑能不能通过服务器配置或者主动杀死 tcp 连接的方式减少 CLOSE_WAIT 连接,来尽可能减少 CLOSE_WAIT 堆积引起的高频率 CLOSE_WAIT 问题(解决不了问题,就解决出现问题的地方)。 我采用的是 killcx https://killcx.sourceforge.net/ ,来模拟 SYNC ,主动关闭连接。但是,不出意外,还是除了意外,连接关不掉!!!其他的连接都可以,但是就是 CLOSE_WAIT 的无法关闭。
最后,只能定时重启 JAVA 程序,主动释放连接才能恢复,在重启的三四天时间不会有问题,过了一个时间点,这个问题又出问题了。。。周而复始。。。
请大家帮忙分析分析,为啥会出现服务器( Linux )保持了 TCP 连接,但是客户端( Windows )没有。到底是我 JAVA 应用的问题,还是网络问题抑或者是客户端关闭连接的问题?给提供点思路,拜求了!
经过两个多月的排查,问题总算缓解了。根本原因大概率是接口中处理逻辑太复杂,导致运行一段时间后tcp请求队列堵塞,导致新的请求进不来直接丢弃。为了不直接去改后台应用逻辑(太复杂了),我解决的方式是通过调整tcp的系统参数:
# 默认值:128 sudo sysctl -w net.ipv4.tcp_max_syn_backlog=4096 # 默认值:5 # 作用:此参数控制的是 TCP 客户端在发起连接时重试 SYN 包的次数。当 TCP 握手的 SYN 包没有收到响应时,客户端会重新发送 SYN 包。默认情况下,Linux 会重试 5 次。 # 调整效果:将该值降低到 2,表示 TCP 客户端在连接失败时会更早放弃重试,这对于高流量场景下减少资源浪费是有帮助的,尤其是在网络不稳定或服务端响应较慢时。降低此值可以加快连接的失败检测和释放未使用的资源。 sudo sysctl -w net.ipv4.tcp_syn_retries=2 # 默认值: net.ipv4.tcp_rmem:4096 87380 6291456 net.ipv4.tcp_wmem:4096 16384 4194304 sudo sysctl -w net.ipv4.tcp_rmem="8192 87380 67108864" sudo sysctl -w net.ipv4.tcp_wmem="8192 87380 67108864" # 默认值:60 # 作用:此参数控制的是处于 FIN_WAIT_2 状态的 TCP 连接在主动关闭一方等待对方确认关闭的最大时间。当 TCP 连接关闭后,主动关闭的一方会进入 FIN_WAIT_2 状态,在等待关闭确认之前会保留连接。 # 调整效果:将该值从 60 秒降低到 30 秒,能够更快地释放资源,特别是在高连接数的环境中,减少由于连接关闭超时导致的资源占用。然而,过短的超时时间可能导致资源清理不完全,因此要根据实际情况调整。 sudo sysctl -w net.ipv4.tcp_fin_timeout=30 # 默认值:4096 # 作用:此参数定义了系统可以保留的“孤儿” TCP 连接的最大数量。孤儿连接是指没有任何应用程序接收的数据包的 TCP 连接。在某些情况下,这些连接会由于异常而无法被正常关闭,导致系统资源被浪费。 # 调整效果:增大该值(比如设置为 327680)能够容纳更多的孤儿连接,适用于需要大量并发连接的场景。不过,如果该值设置过大,可能会导致系统资源被大量浪费,增加系统负担。 sysctl -w net.ipv4.tcp_max_orphans=327680 # 默认值:7200(即 2 小时) # 作用:此参数控制的是 TCP 连接在进入“空闲”状态后发送第一个 keepalive 探测包的时间,单位是秒。当 TCP 连接处于空闲状态时,系统会定期发送 keepalive 包,以确保连接仍然有效。 # 调整效果:将该值从默认的 2 小时降低到 600 秒(即 10 分钟),会加快检测空闲连接的速度,及时清理长时间没有数据传输的连接。对于需要及时发现死连接的应用场景(如 Web 服务)较为合适。 sysctl -w net.ipv4.tcp_keepalive_time=600 # 默认值:75 # 作用:此参数控制的是如果 TCP 连接处于空闲状态,keepalive 探测包的发送间隔时间,单位为秒。即在发送第一个探测包后,每隔多少秒发送一个探测包。 # 调整效果:将该值调整为 60 秒,表示每 60 秒发送一个 keepalive 包,检测连接是否仍然有效。这个值适用于需要快速检测死连接的场景。 sysctl -w net.ipv4.tcp_keepalive_intvl=60 # 默认值:9 # 作用:此参数控制的是在认为连接已断开之前,最大重试的探测次数。即,如果一个 TCP 连接没有响应 keepalive 包,系统会重试发送 keepalive 包的次数。 # 调整效果:将该值从默认的 9 次降低到 5 次,意味着在 5 次探测包失败后,系统会认为连接断开并关闭该连接。降低此值有助于尽早发现和关闭死连接,但设置得过低也可能导致正常连接被误判为断开。 sysctl -w net.ipv4.tcp_keepalive_probes=5
回过头来看,1L大哥就已经给出了答案。
1
dode 76 天前
CLOSE_WAIT 问题 配置相关内核参数
|
2
mango88 76 天前
60s 服务端没发送 FIN 帧, 大概率是你的代码有问题
|
3
1423 75 天前
是你 JAVA 应用的问题
|
4
palfortime 75 天前 via Android
1. 服务端用了哪个 java 的框架。
2. 客户端与服务端之间是直连吗?有用了正向或反向代理吗? 3. 服务端有发起其它 TCP 连接吗?确认 close_wait 的连接是客户端请求服务端的? |
5
Monad 75 天前 via iPhone 1
close_wait 不是你服务端没有 close 连接嘛 又不是 time_wait 大量 close_wait 是 bug 吧
|
6
Mohanson 75 天前 1
|
7
clester 75 天前 via iPhone
对双端发送 reset😏
|
8
tyrantZhao 75 天前
close_wait 堆积一般都是短链接集中关闭或者服务过于频繁来不及关闭,需要根据具体情况来决定怎么改。(背的八股用上了?)
|
9
Mohanson 75 天前
还有一种可能就是服务端没有调用 Close. 总之是代码写的有问题.
|
10
ysc3839 75 天前 via Android 1
感觉是因为你没把 write stream 关掉,对端一直等不到你的 FIN ,超时发送 RST 了。
我个人觉得“四次挥手”这说法有问题,我个人会称作“两端关闭”。TCP 连接可以看成两条方向相反的管道,其中一端发送 FIN ,代表关闭自己这端的写管道(即对端的读管道),一端关闭后,另一端还是能继续写数据的,直到对端也关闭管道,两条管道都关闭了,TCP 连接断开。 如果用“四次挥手”的话,就会给人感觉一端发送 FIN 后,另一端也会自动发送 FIN 关闭连接,但其实并不是,需要主动关闭。 同时没记错的话 FIN 是可以和其他包合并的,就会出现不是正好四个包断开连接的情况。 |
11
arloor 75 天前 1
被动关闭方(这里是 linux 服务端)没有调用 socket 的 close 方法,也就是没有发出 FIN 。
所以你 java 应用到底用的啥框架,如果是 spring 应该不会有这种问题。 如果是手搓的,那大概就是没有 close |
12
cppc 75 天前
CLOSE_WAIT 一般是你自己没有关闭连接导致的,如果你是自己手搓的 Web 服务端网络框架的话,注意排查一下
|
13
oneisall8955 75 天前
这是一个好问题,mark
|
14
seWindows 75 天前
CLOSE_WAIT 是你忘了关了,对端断开是 TIME-WAIT/FIN-WAIT
|
15
kangyue9999 75 天前 via Android
杀死制造请求的人
|
16
onesuren 75 天前
|
17
cheng6563 75 天前
你 Web 服务是用框架启的吧,框架应该不会忘记 Close 。
翻下 std 有无输出 OOM 错误吧。我是建议直接加 -XX:+ExitOnOutOfMemoryError |
18
aizya OP @palfortime
1. 服务端提供 REST 接口使用的是 RESTeasy ( JAX-RS ) 2. 直连的,没有用代理 3. 服务端没有再调其他三方的接口,CLOSE_WAIT 对应的 ip 地址对应的就是客户端的。 系统运行一段时间后才会慢慢出现 CLOSE_WAIT 问题,如果是本身没有关闭连接应该一请求就出来,很困惑不清楚如何排查。 |
19
aizya OP @Mohanson #6 感谢,这篇文章是目前看到最匹配我遇到的问题的。但是您提到的 accept 失败,是由于对应 JAVA 进程的 open files 增多引起的?我翻看了日志,没有体现 too much open files 相关的日志,请问有其他排查的方法吗😭
|
20
aizya OP @onesuren 受教了,感觉这个跟我的问题还不太一样,文章中是由于有代码中增加了休眠(运行时间过长引起的。)应该是每次请求都会增加一条 CLOSE_WAIT? 我的情况是系统运行前几天一切正常,之后就会出现问题。
|
21
seedhk 75 天前
CLOSE_WAIT 很明显就是服务端问题
导出线程栈信息,找到所有 CLOSE_WAIT 的线程,看堆栈找对应代码 |
22
iceheart 75 天前 via Android
客户端没连接是因为已经 close 了,
客户端 close 了服务端才有 CLOSE_WAIT 服务端收到断开通知就要 close CLOSE_WAIT 的字面意思就是 等待(应用程序) 来调用 close |
23
palfortime 74 天前 via Android
@aizya 不了解这个框架,你可以试一下跑个测试的服务,让客户端的进程跑一个请求就退出,看看会不会产生。
可以在服务端 jstack 一下,看看有没有工作线程是卡住了。可能由于工作线程卡住,导致框架无法处理 close 请求。 同时可以在服务端加载一下 opentelemetry 的 javaagent 收集一下 trace 和 metric ,看看是否有服务端的异常。 |
24
xxxbin 74 天前
有意思。那么各位大佬,CLOSE_WAIT 的 tcp 该咋关?
|