timewait的tcp连接数过多导致CPU卡死在100%与磁盘一直保持在较高的读?

timewait的tcp连接数过多导致CPU卡死在100%与磁盘一直保持在较高的读?

喜欢这个问题 | 分享 | 新建回答

回答

jerkzhang

May 7, 2025
1 赞

配置一台高并发的服务器,做如下:

修改 /etc/sysctl.conf 文件,

sudo vim /etc/sysctl.conf

添加如下:

net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_max_tw_buckets = 200000

减少 tcp_fin_timeout:设置 tcp_fin_timeout 参数,减少 TIME_WAIT 状态的持续时间。默认该参数是60s,我上述改成了30s,如果有需求,可以改到10s。

修改完毕后运行如下命令以生效。

sysctl -p



防止文件描述符耗尽。

sudo vim /etc/security/limits.conf

添加如下几行:

* hard nofile 65536
* soft nofile 65536


sudo vim /etc/systemd/system.conf

找到相关位置,或者文件末尾添加如下两行:

DefaultLimitNOFILE=65536
DefaultLimitNPROC=65536


sudo vim /etc/systemd/user.conf

找到相关位置,或者文件末尾添加如下两行:

DefaultLimitNOFILE=65536
DefaultLimitNPROC=65536


最后运行“sudo systemctl daemon-reload”以生效。


为了测试是否生效,可以命令行输入“ulimit -n”看输出的结果是不是65536。



jerkzhang

May 7, 2025
0 赞

根据您的描述,问题的根源是 TIME_WAIT 状态的 TCP 连接数过多,导致系统资源(特别是文件描述符)耗尽,进而引发磁盘读取和 CPU 使用率居高不下的问题。即使 TIME_WAIT 连接数减少,系统仍然可能因为之前的资源耗尽或逻辑阻塞而无法恢复正常。以下是针对这种情况的详细分析和解决方案:

1. TIME_WAIT 连接数过多的原因

TIME_WAIT 是 TCP 连接关闭时的一个状态,表示连接已经关闭,但仍然需要等待一段时间以确保所有数据包都已正确传输。过多的 TIME_WAIT 连接可能是由于以下原因导致的:

高并发请求:代理服务器在高并发场景下频繁建立和关闭连接。

连接关闭策略不当:代理服务器或目标服务器的连接关闭策略不合理,导致连接不能及时进入 TIME_WAIT 状态并清理。

操作系统参数配置不当:操作系统的 TCP 参数(如 tcp_fin_timeout)设置不合理,导致 TIME_WAIT 状态的连接占用时间过长。

2. TIME_WAIT 连接数过多的影响

过多的 TIME_WAIT 连接会导致以下问题:

文件描述符耗尽:每个 TCP 连接都会占用一个文件描述符,过多的 TIME_WAIT 连接可能导致文件描述符耗尽。

系统资源耗尽:文件描述符耗尽后,新的连接无法建立,导致请求被阻塞。

磁盘和 CPU 资源占用:系统在尝试清理 TIME_WAIT 连接时会消耗 CPU 资源,同时可能因为日志记录等操作导致磁盘 I/O 增加。

3. 解决方案

(1)优化 TCP 参数

调整操作系统的 TCP 参数,减少 TIME_WAIT 状态的连接占用时间:

减少 tcp_fin_timeout:设置 tcp_fin_timeout 参数,减少 TIME_WAIT 状态的持续时间。例如:

echo 10 > /proc/sys/net/ipv4/tcp_fin_timeout

或者在 /etc/sysctl.conf 中添加:

net.ipv4.tcp_fin_timeout = 10

然后运行:

sysctl -p

启用 tcp_tw_reuse 和 tcp_tw_recycle:允许系统重用 TIME_WAIT 状态的连接,减少资源占用。在 /etc/sysctl.conf 中添加:

net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1

然后运行:

sysctl -p

(2)优化代理服务器的连接管理

使用连接池:对于代理请求,使用连接池来管理与目标服务器的连接,避免频繁建立和关闭连接。例如,使用 http.Client 的 Transport 配置:

client := &http.Client{
Transport: &http.Transport{
MaxIdleConns: 100, // 最大空闲连接数
MaxIdleConnsPerHost: 10, // 每个主机的最大空闲连接数
IdleConnTimeout: 30 * time.Second, // 空闲连接超时时间
},
}

设置合理的超时时间:为代理请求设置合理的超时时间,避免长时间等待目标服务器响应。例如:

client := &http.Client{
Timeout: 5 * time.Second, // 设置超时时间
}

(3)优化文件描述符限制

增加文件描述符限制:确保系统的文件描述符限制足够高。在 /etc/security/limits.conf 中设置:

* hard nofile 65536
* soft nofile 65536

然后重启系统或使用 ulimit 命令临时调整:

ulimit -n 65536

(4)监控和清理 TIME_WAIT 连接

监控 TIME_WAIT 连接数:使用 netstat 或 ss 命令监控 TIME_WAIT 连接数:

netstat -an | grep TIME_WAIT | wc -l
ss -s | grep TIME_WAIT

清理 TIME_WAIT 连接:如果系统资源耗尽,可以尝试手动清理 TIME_WAIT 连接。例如,使用 sysctl 调整参数:

echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle

(5)优化磁盘和 CPU 使用

减少日志记录:如果代理服务器频繁记录日志,可能会导致磁盘 I/O 增加。可以将日志输出到其他存储系统(如日志服务器或云存储),减少磁盘写入。

优化垃圾回收:如果代理服务器的内存占用过高,可能会导致频繁的垃圾回收(GC),进而影响 CPU 使用率。可以使用 pprof 工具分析内存使用情况,优化代码逻辑,减少不必要的内存分配。

限制并发请求:在代理服务器中实现限流机制,限制每个客户端的请求频率。例如,使用令牌桶算法或漏桶算法:

rateLimiter := rate.NewLimiter(rate.Every(1*time.Second), 100) // 每秒最多 100 个请求
if !rateLimiter.Allow() {
return c.String(http.StatusTooManyRequests, "Too many requests")
}

4. 排查和解决磁盘和 CPU 卡住的问题

即使 TIME_WAIT 连接数减少,系统仍然可能因为之前的资源耗尽或逻辑阻塞而无法恢复正常。以下是进一步排查和解决的方法:

(1)监控系统资源

CPU 使用率:使用 top 或 htop 命令查看 CPU 使用情况,确认是否有进程占用过高 CPU。

磁盘 I/O:使用 iostat 或 iotop 命令查看磁盘 I/O 情况,确认是否有异常的磁盘读写操作。

内存使用:使用 free -m 命令查看内存使用情况,确认是否有内存泄漏。

(2)分析程序状态

使用 pprof 分析 Goroutine 和 CPU 使用情况:

go tool pprof http://<your-server>:<pprof-port>/debug/pprof/goroutine
go tool pprof http://<your-server>:<pprof-port>/debug/pprof/profile

查看是否有 Goroutine 阻塞或异常的热点函数。

检查日志:查看代理服务器的日志,确认是否有错误或异常信息。

(3)逐步恢复系统状态

重启服务:如果系统状态无法恢复,可以尝试重启代理服务器服务,释放资源。

逐步减少负载:减少代理服务器的负载,例如关闭部分客户端连接,逐步恢复系统状态。

总结

TIME_WAIT 连接数过多是导致系统资源耗尽的直接原因,而磁盘和 CPU 的高占用可能是由于资源耗尽后的连锁反应。解决这个问题需要从以下几个方面入手:

优化 TCP 参数,减少 TIME_WAIT 状态的连接占用时间。

优化代理服务器的连接管理,使用连接池和合理的超时时间。

增加文件描述符限制,确保系统有足够的资源。

监控和清理 TIME_WAIT 连接,避免资源耗尽。

优化磁盘和 CPU 使用,减少不必要的日志记录和内存分配。

逐步恢复系统状态,必要时重启服务。

通过这些优化措施,可以有效缓解高并发场景下的性能问题,确保代理服务器的稳定运行。