为了能彻底讲清楚TIME_WAIT的原理及解决办法,本系列一共有4篇
彻底理解并解决服务器出现大量TIME_WAIT - 第一篇_YZF_Kevin的博客-CSDN博客
彻底理解并解决服务器出现大量TIME_WAIT - 第二篇_YZF_Kevin的博客-CSDN博客
彻底理解并解决服务器出现大量TIME_WAIT - 第三篇_YZF_Kevin的博客-CSDN博客
彻底理解并解决服务器出现大量TIME_WAIT - 第四篇_YZF_Kevin的博客-CSDN博客
首先我们说下状态 TIME_WAIT 出现的原因
TCP的新建连接,断开连接的流程和各个状态,如下图所示

由上图可知:TIME_WAIT 是主动断开连接的一方会出现的,客户端,服务器都有可能出现
当客户端主动断开连接时,发出最后一个ACK后就会处于 TIME_WAIT状态
当服务器主动断开连接时,发出最后一个ACK后就会处于 TIME_WAIT状态
结论:TIME_WAIT 是必然会出现的状态,是正常现象,且会定时回收
TIME_WAIT 状态持续2MSL时间,MSL就是maximum segment lifetime(最大报文段的生命期),这是一个IP数据包能在互联网上生存的最长时间,超过这个时间将在网络中消失(被丢弃)。RFC 793中规定MSL为2分钟,实际应用中,可能为30S,1分钟,2分钟。
请注意两个状态,一个是TIME_WAIT,一个是CLOSE_WAIT,完全不同的两个状态
TIME_WAIT 出现在主动断开方,发出最后一个ACK后
CLOSE_WAIT 出现在被动断开方,收到主动断开方的FIN,发出自己的ACK后
问题:为什么要有TIME_WAIT状态?
1. 为了可靠地关闭TCP连接
举例:我们把主动断开连接的一方称为C端,被动断开连接的一方称为S端,由于网络不可靠,C端发送的最后一个ACK报文可能没成功发送到S端,那么S端就会重新发上一个报文即FIN,如果C端处于TIME_WAIT状态下,就可以重新发送报文ACK,然后重新计时2MSL时间才会进入CLOSED状态,S端收到ACK后就可以正常关闭TCP连接了。反之,如果这时C端处于 CLOSED 状态 ,就会响应 RST报文而不是ACK报文,那S端会认为这是一个错误,只能异常关闭TCP连接
2. 防止上一次连接中的包,迷路后重新出现,影响新连接
由于网络的不可靠,TCP分节可能因为路由器异常而“迷途”,在迷途期间,TCP发送端会因确认超时而重发这个分节,这个分节最终被发送到对方时,对方可能已经是一个新的连接了,由此造成混乱。举例:关闭一个TCP链接后,马上又创建了一个相同的IP地址和端口之间的TCP链接,后一个链接被称为前一个链接的化身(incarnation),那么此时有可能出现这种状况,前一个链接的迷途重复分节在前一个链接终止后出现了,从而被误解成从属于新的连接的数据。为了不出现这种混乱,TCP不容许处于TIME_WAIT状态的连接立即启动一个新连接,由于TIME_WAIT状态持续2MSL,就能够保证当成功创建一个TCP链接的时候,来自前一个连接的迷途重复分节已经在网络中消逝
注意close() 和 shutdown()的区别
close()其实只是将socket fd的引用计数减1,只有当该socket fd的引用计数减至0时,TCP传输层才会发起4次握手从而真正关闭连接。而shutdown则可以直接发起关闭连接所需的4次握手,而不用受到引用计数的限制
close()会终止TCP的双工链路。由于TCP连接的全双工特性,可能会存在这样的应用场景:local peer不会再向remote peer发送数据,而remote peer可能还有数据需要发送过来,在这种情况下,如果local peer想要通知remote peer自己不会再发送数据但还会继续收数据这个事实,用close()是不行的,而shutdown()可以完成这个任务
服务器短时间内大量的TIME_WAIT出现,才是问题
会引发以下问题
1. 由于处于TIME_WAIT状态,连接并未关闭,占据了大量的CPU,内存,文件描述符等,造成新的连接无法建立,客户端表现就是连接失败
2. 如果服务器上同时有nginx,且nginx由于反向代理,那么还会占用很多端口(S端处于TIME_WAIT,该连接的另一方即C端需独占一个端口,C端是由nginx代理建立的),要知道端口是有限的,最多65535,一旦端口占用完,无论服务器配置如何高,新连接都无法建立了,客户端表现仍然是连接失败。而且即使端口没用完,因为建立连接时C端是随机一个位置,向后遍历查询可用端口的,此时就需要大量的遍历才能找到,效率上也很差
短时间内大量TIME_WAIT出现的根本原因:高并发且持续的短连接
1. 业务上使用了持续且大量的短连接,纯属设计缺陷,例如爬虫服务器就有可能出现这样的问题
2. http请求中connection的值被设置成close,因为服务器处理完http请求后会主动断开连接,然后这个连接就处于TIME_WAIT状态了。持续时间长且量级较大的话,问题就显现出来了。http协议1.0中,connection默认为close,但在http1.1中connection默认行为是keep-alive,就是因为这个原因
3. 服务器被攻击了,攻击方采用了大量的短连接
重点:解决办法
1. 代码层修改,把短连接改为长连接,但代价较大
2. 修改 ip_local_port_range,增大可用端口范围,比如1024 ~ 65535
3. 客户端程序中设置socket的 SO_LINGER 选项
4. 打开 tcp_tw_recycle 和tcp_timestamps 选项,有一定风险,且linux4.12之后被废弃
5. 打开 tcp_tw_reuse 和 tcp_timestamps 选项
6. 设置 tcp_max_tw_buckets 为一个较小的值
下一篇博客,我们将会一一讲解上面各个办法的原理