原因
回顾之前的印象笔记时,查看到之前工作时记录的问题,查看之前解决办法时决定重新总结一下
背景
- 后端使用nodejs处理连接,主要是做验证
- 前端使用nginx做控制,做7层反向proxy
- 验证时间很短,所以从nginx通过proxy_pass到后端的realserver之间使用的是短连接
现象
某次大型活动(国足比赛),请求数大增,nginx服务器出现大量TIME_WAIT状态的连接,查看方式:
netstat方法:
shell> netstat -n | awk '/^tcp/ {++state[$NF]} END {for(key in state) print key,"\t",state[key]}'
ss查看比netstat要快:
ss -s查看TW的数量
解决办法
Nginx做前端Proxy时TIME_WAIT过多的问题
要点
- 修改系统预留端口,防止自己进程监听的大于1024的端口被占用
- 解决TW过多:修改nginx upstream配置中的keepalive时长并且在server段中使用http1.1(1.0不支持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 "";
...
}
}
其他
通过这次解决过程顺便记录涉及到的其他内容
TW状态产生的过程
TCP三次握手和四次挥手就不再啰嗦了,从上图可以看出,主动发送FIN的一方会受到TIME-WAIT的“惩罚”(就像主动提出分手会受到道德谴责一段时间)由RFC规定为2MSL,根据不同的操作系统MSL(Maximum Segment Lifetime)设定的时间不一样,Linux一般是30s且该值无法通过内核参数修改,必须自己重新编译内核才能修改此参数。
穿插一点MSL的知识:MSL指的是报文段的最大生存时间,如果报文段在网络活动了MSL时间,还没有被接收,那么会被丢弃。关于MSL的大小,RFC 793协议中给出的建议是两分钟,不过实际上不同的操作系统可能有不同的设置,以Linux为例,通常是半分钟,两倍的MSL就是一分钟,也就是60秒,并且这个数值是硬编码在内核中的,也就是说除非你重新编译内核,否则没法修改它:
#define TCP_TIMEWAIT_LEN (60*HZ)
TW产生的原因
有的人看到这就可能会问:为什么需要TIME-WAIT这个状态?这个状态纯粹是坑爹啊。。。其实主要是由于早期TCP设计时是用于建立有保障的连接的,由于当时网络底层的设备远没有现在这么发达,所以四次挥手时为了防止出现最后一个ACK对方没有收到并再次重传FIN接着导致:
- 连接已经关闭,再次收到FIN回复一个RST;
- 新的连接已经建立,FIN包会干扰到新的连接;
总之,不管怎么样都会导致TCP不再可靠因此TW状态是必须存在的,由产生原因很容易想到为什么是2MSL,因为最坏的情况下一来一回刚好两段MSL。
如何控制TIME-WAIT数量
网络上大部分文章谈到解决TW状态都会提到以下内核参数
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
tcp_timestamps
net.ipv4.tcp_fin_timeout
实际上 net.ipv4.tcp_fin_timeout 控制的是fin_wait_2这个状态的超时时间,重用回收和时间戳的机制可以看下面的文章描述:
这个链接说明很清楚了
要点:
- ip_conntrack:顾名思义就是跟踪连接。(引入问题过多不建议使用)
- tcp_tw_recycle:顾名思义就是回收TIME_WAIT连接。(NAT模式下有问题,产生时间戳混乱,不建议使用)
- tcp_tw_reuse:顾名思义就是复用TIME_WAIT连接。(作为连接的发起方(Client)有用(需网络稳定),作为被连接方基本没用)
- tcp_max_tw_buckets:顾名思义就是控制TIME_WAIT总数。(治标不治本,限制tw队列大小系统日志会报「TCP: time wait bucket table overflow」)
结论:
有时候,如果我们换个角度去看问题,往往能得到四两拨千斤的效果。前面提到的例子:客户端向服务端发起HTTP请求,服务端响应后主动关闭连接,于是TIME_WAIT便留在了服务端。这里的关键在于主动关闭连接的是服务端!在关闭TCP连接的时候,先出手的一方注定逃不开TIME_WAIT的宿命,套用一句歌词:把我的悲伤留给自己,你的美丽让你带走。如果客户端可控的话,那么在服务端打开KeepAlive,尽可能不让服务端主动关闭连接,而让客户端主动关闭连接,如此一来问题便迎刃而解了。