# http - TIME_WAIT,CLOSE_WAIT异常
# 遇到的问题
request.js
在启用 proxy 来爬取数据的时候,进程的 active handle
一直在增加。
# 排查问题
- 使用
netstat
命令查看当前 tcp 链接情况
netstat -na | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
#LAST_ACK 4
#LISTEN 16
#CLOSE_WAIT 92 // 很多!!!
#ESTABLISHED 18
#FIN_WAIT1 3
#FIN_WAIT2 3
#TIME_WAIT 98 // 很多!!!
#SYN_SENT 14
2
3
4
5
6
7
8
9
10
11
常用的三个状态是:ESTABLISHED 表示正在通信,TIME_WAIT 表示主动关闭,CLOSE_WAIT 表示被动关闭。
CLOSED 表示socket连接没被使用。
LISTENING 表示正在监听进入的连接。
SYN_SENT 表示正在试着建立连接。
SYN_RECEIVED 进行连接初始同步。
ESTABLISHED 表示连接已被建立。
CLOSE_WAIT 表示远程计算机关闭连接,正在等待socket连接的关闭。
FIN_WAIT_1 表示socket连接关闭,正在关闭连接。
CLOSING 先关闭本地socket连接,然后关闭远程socket连接,最后等待确认信息。
LAST_ACK 远程计算机关闭后,等待确认信号。
FIN_WAIT_2 socket连接关闭后,等待来自远程计算机的关闭信号。
TIME_WAIT 连接关闭后,等待远程计算机关闭重发。
可以看到,有很多的连接是 TIME_WAIT
和 CLOSE_WAIT
,并且存在很久,导致 pm2 看到内存占用越来越大,active handle 也越来越多。
# 解决流程
1. TIME_WAIT
:
这种情况比较常见,一些
爬虫服务器
或者WEB服务器
(如果网管在安装的时候没有做内核参数优化的话)上经常会遇到这个问题。
TIME_WAIT是主动关闭连接的一方保持的状态,对于爬虫服务器来说他本身就是“客户端”,在完成一个爬取任务之后,他就会发起主动关闭连接,从而进入TIME_WAIT的状态,然后在保持这个状态2MSL(max segment lifetime)时间之后,彻底关闭回收资源。为什么要这么做?明明就已经主动关闭连接了为啥还要保持资源一段时间呢?这个是TCP/IP的设计者规定的,主要出于以下两个方面的考虑:
a. 防止上一次连接中的包,迷路后重新出现,影响新连接(经过2MSL,上一次连接中所有的重复包都会消失) b. 可靠的关闭TCP连接。在主动关闭方发送的最后一个 ack(fin) ,有可能丢失,这时被动方会重新发fin, 如果这时主动方处于 CLOSED 状态 ,就会响应 rst 而不是 ack。所以主动方要处于 TIME_WAIT 状态,而不能是 CLOSED 。另外这么设计TIME_WAIT 会定时的回收资源,并不会占用很大资源的,除非短时间内接受大量请求或者受到攻击。
解决思路很简单,就是让服务器能够快速回收和重用那些TIME_WAIT的资源。
修改 /etc/sysctl.conf
文件配置,没有这个文件就新建一个:
net.ipv4.tcp_keepalive_time = 200
net.ipv4.tcp_keepalive_probes = 3
net.ipv4.tcp_keepalive_intvl = 15
2
3
修改之后执行 /sbin/sysctl -p
使参数生效。
2. CLOSE_WAIT
:
TIME_WAIT状态可以通过优化服务器参数得到解决,因为发生TIME_WAIT的情况是服务器自己可控的,要么就是对方连接的异常,要么就是自己没有迅速回收资源,总之不是由于自己程序错误导致的。
但是CLOSE_WAIT就不一样了,从上面的图可以看出来,如果一直保持在CLOSE_WAIT状态,那么只有一种情况,就是在对方关闭连接之后服务器程序自己没有进一步发出ack信号。换句话说,就是在对方连接关闭之后,程序里没有检测到,或者程序压根就忘记了这个时候需要关闭连接,于是这个资源就一直被程序占着。
所以,最后检查代码,是代码层面的问题
考虑到本次问题一个很明晰的特点就是,使用了代理服务器去请求 https
的请求。所以就重点关注了 request
库 socket,https,proxy 相关的问题,最后找到了一个 issue (opens new window)。其实一开始就看到了issue,但是不知道为什么没有注意???蒸腾了1,2天之后再次看到它 - - 😢
p.s Error: socket hang up (opens new window) 看到一个配置,请求的 header 里面,默认是 keepAlive:true
,这个时候,可能保持很多socket请求。所有可以配置
const HttpsAgent = require('agentkeepalive').HttpsAgent;
const agent = new HttpsAgent({
freeSocketTimeout: 4000
});
2
3
4
5
6
# 解决方案
最终解决方案,
- 针对
TIME_WAIT
修改服务器配置 - 针对
CLOSE_WAIT
请求的时候设置 headers 的Connection: 'close''
const rp = require('request-promise');
rp({
url: 'https://www.example.com',
proxy: 'http://1.1.1.1:8888',
headers:{
Connection: 'close'
}
})
2
3
4
5
6
7
8
9
10
# 补充
一个小插曲,针对 TIME_WAIT
其实都没有去进行服务器配置的修改,但是早上突然发现还有大量 LAST_ACK
的状态。度娘了一下,发现好像被针对了 - -。遂进行了一番学习和配置。
# 当keepalive打开的情况下,TCP发送keepalive消息的频率。(默认值是7200(2小时),服务器建议设为1800)
net.ipv4.tcp_keepalive_time = 200
# 在近端丢弃TCP连接之前﹐要进行多少次重试。默认值是7个﹐在大负载服务器上建议调整为3)
net.ipv4.tcp_keepalive_probes = 3
# 探测消息发送的频率,乘以tcp_keepalive_probes就得到对于从开始探测以来没有响应的连接杀除的时间。(默认值为75秒,推荐设为15秒)
net.ipv4.tcp_keepalive_intvl = 15
2
3
4
5
6
最后还配置了 ip 限制,针对发送请求的几个 ip 段。