timewait的tcp连接数过多导致CPU卡死在100%与磁盘一直保持在较高的读?
jerkzhang
配置一台高并发的服务器,做如下:
修改 /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
根据您的描述,问题的根源是 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 使用,减少不必要的日志记录和内存分配。
逐步恢复系统状态,必要时重启服务。
通过这些优化措施,可以有效缓解高并发场景下的性能问题,确保代理服务器的稳定运行。