故障描述
Resin服务的端口为8080,执行 lsof -i :8080
命令出现大量的ESTABLISHED连接:
然后执行netstat -n | awk '/^tcp/ {++y[$NF]} END {for(w in y) print w, y[w]}'
命令发现存在大量状态为TIME_WAIT的连接:
简单来说,ESTABLISHED表示正在进行网络连接的数量,TIME_WAIT表示表示等待系统主动关闭网络连接的数量,CLOSE_WAIT表示被动等待程序关闭的网络连接数量,因此TIME_WAIT跟服务器的配置有关,而CLOSE_WAIT跟程序进行网络连接有关了,通常是程序没有主动关闭网络连接所致。
逻辑代码中没有释放网络资源
首先检查确保,在逻辑代码中是否没有及时释放网络资源。通常如果出现大量的CLOSE_WAIT说明程序在进行网络连接的时候没有主动关闭连接,比如使用HttpClient调用网络连接的时候,当出现错误的时候没有调用abort()方法,或者网络连接结束之后没有调用close方法等等,具体在源代码中进行排查即可。
Nginx设置
Nginx 1.1以上版本的upstream
开始支持keep-alive参数了,可以开启proxy的keep-alive来减少TPC的连接。
其实现原理摘抄如下:
Nginx目前的upstream连接建立和获取的机制如下图。Nginx会在一开始创建connection pool(进程间不共享,可以避免锁),提供给所有向前/后的连接。
如果要实现upstream长连接,则每个进程需要另外一个connection pool,里面都是长连接。一旦与后端服务器建立连接,则在当前请求连接结束之后不会立即关闭连接,而是把用完的连接保存在一个keepalive connection pool里面,以后每次需要建立向后连接的时候,只需要从这个连接池里面找,如果找到合适的连接的话,就可以直接来用这个连接,不需要重新创建socket或者发起connect()。这样既省下建立连接时在握手的时间消耗,又可以避免TCP连接的slow start。如果在keepalive连接池找不到合适的连接,那就按照原来的步骤重新建立连接。假设连接查找时间可以忽略不计,那么这种方法肯定是有益而无害的(当然,需要少量额外的内存)。
可以使用如下配置:
upstream http_backend {
server 127.0.0.1:8080;
keepalive 16;
}
server {
...
location /http/ {
proxy_pass http://http_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
...
}
}
需要注意的是,keepalive
参数并非限定upstream server总的连接数,而是指定了持续连接的数量,这个参数不能设置太大,否则会造成upstream无法建立新的连接的问题。在实际的应用中不断地去调整该参数,保持一种平衡。
如果Nginx没有使用HTTP长连接请求后端,后端处理完后就主动关闭连接,因此TIME_WAIT发生在后端;如果Nginx使用了HTTP长连接,建立连接后,后端认为是「长连接」而不会主动关闭连接(一般有个空闲超时),关闭连接由 Nginx 来做了,所以 Nginx 会出现大量的 TIME_WAIT。
Linux参数调整
如果没有使用Nginx的话,也可以对Linux的系统参数进行调整。对 /etc/sysctl.conf 文件做如下设置(包含每个配置项的说明):
#对于一个新建连接,内核要发送多少个 SYN 连接请求才决定放弃,不应该大于255,默认值是5,对应于180秒左右时间
net.ipv4.tcp_syn_retries=2
#net.ipv4.tcp_synack_retries=2
#表示当keepalive起用的时候,TCP发送keepalive消息的频度。缺省是2小时,改为300秒
net.ipv4.tcp_keepalive_time=1200
net.ipv4.tcp_orphan_retries=3
#表示如果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2状态的时间
net.ipv4.tcp_fin_timeout=30
#表示SYN队列的长度,默认为1024,加大队列长度为8192,可以容纳更多等待连接的网络连接数。
net.ipv4.tcp_max_syn_backlog = 4096
#表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭
net.ipv4.tcp_syncookies = 1
#表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭
net.ipv4.tcp_tw_reuse = 1
#表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭
net.ipv4.tcp_tw_recycle = 1
##减少超时前的探测次数
net.ipv4.tcp_keepalive_probes=5
##优化网络设备接收队列
net.core.netdev_max_backlog=3000
修改保存之后执行 /sbin/sysctl -p
让参数生效即可。这里需要说明几点:
(1) net.ipv4.tcp_tw_reuse和net.ipv4.tcp_tw_recycle的开启都是为了回收处于TIME_WAIT状态的资源,net.ipv4.tcp_fin_timeout这个时间可以减少在异常情况下服务器从FIN-WAIT-2转到TIME_WAIT的时间,net.ipv4.tcpkeepalive*一系列参数,是用来设置服务器检测连接存活的相关配置。
(2) 如果客户端访问是NAT网络(即:局域网内多台电脑使用一个公网IP上网),不要开启net.ipv4.tcp_tw_recycle或者在服务端关闭time stamp (echo 0 /proc/sys/net/ipv4/tcp_timestamps
),参考这篇文章:不要在linux上启用net.ipv4.tcp_tw_recycle参数和【经验总结】tcp_tw_recycle参数引发的故障。
(3) Windows服务器的解决方法请参考这篇文字:在 Windows 上遇到非常多 TIME_WAIT 連線時應如何處理。
参考资料:
HttpClient连接池抛出大量ConnectionPoolTimeoutException: Timeout waiting for connection异常排查
httpClient connection not closing
Nginx做前端Proxy时TIME_WAIT过多的问题
再叙TIME_WAIT
Nginx upstream 长连接
关于 Nginx upstream keepalive 的说明
优化nginx upstream连接
记一次压测引起的nginx负载均衡性能调优
再谈应用环境下的TIME_WAIT和CLOSE_WAIT
【经验总结】tcp_tw_recycle参数引发的故障
不要在linux上启用net.ipv4.tcp_tw_recycle参数
Tcp 连接出现大量ESTABLISHED连接
resin 调优