服务器的TIME_WAIT和CLOSE_WAIT

 

1.1. 服务器的TIME_WAIT和CLOSE_WAIT

在服务器的日常维护过程中,排查TIME_WAIT和CLOSE_WAIT问题需要用到下面的命令:

netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

TIME_WAIT 814
CLOSE_WAIT 1
FIN_WAIT1 1
ESTABLISHED 634
SYN_RECV 2
LAST_ACK 1

常用的三个状态是:ESTABLISHED 表示正在通信,TIME_WAIT 表示主动关闭,CLOSE_WAIT 表示被动关闭。

查看网络状态,如果出现以下两种问题,服务器一般都出了异常。

  • 服务器保持了大量TIME_WAIT状态
  • 服务器保持了大量CLOSE_WAIT状态

因为linux分配给一个用户的文件句柄是有限的,而TIME_WAIT和CLOSE_WAIT两种状态如果一直被保持,那么意味着对应数目的通道就一直被占着,一旦达到句柄数上限,新的请求就无法被处理了,接着就是大量Too Many Open Files异常,最终导致tomcat崩溃。

1.1.1. 什么是TIME-WAIT和CLOSE-WAIT ?

由于socket是全双工的工作模式,一个socket的关闭,是需要四次握手来完成的:

  1. 主动关闭连接的一方,调用close();协议层发送FIN包 ;
  2. 被动关闭的一方收到FIN包后,协议层回复ACK;然后被动关闭的一方,进入CLOSE_WAIT状态,主动关闭的一方等待对方关闭,则进入FIN_WAIT_2状态;此时,主动关闭的一方等待被动关闭一方的应用程序,调用close操作 ;
  3. 被动关闭的一方在完成所有数据发送后,调用close()操作;此时,协议层发送FIN包给主动关闭的一方,等待对方的ACK,被动关闭的一方进入LAST_ACK状态;
  4. 主动关闭的一方收到FIN包,协议层回复ACK;此时,主动关闭连接的一方,进入TIME_WAIT状态;而被动关闭的一方,进入CLOSED状态 ;
  5. 等待2MSL时间,主动关闭的一方,结束TIME_WAIT,进入CLOSED状态 ;

通过上面的一次socket关闭操作,可以得出以下几点:

  1. 主动关闭连接的一方 – 也就是主动调用socket的close操作的一方,最终会进入TIME_WAIT状态 ;
  2. 被动关闭连接的一方,有一个中间状态,即CLOSE_WAIT,因为协议层在等待上层的应用程序,主动调用close操作后才主动关闭这条连接 ;
  3. TIME_WAIT会默认等待2MSL时间后,才最终进入CLOSED状态;
  4. 在一个连接没有进入CLOSED状态之前,这个连接是不能被重用的

TIME_WAIT并不可怕,CLOSE_WAIT才可怕,因为CLOSE_WAIT很多,表示说要么是你的应用程序写的有问题,没有合适的关闭socket。要么是服务器CPU处理不过来(CPU太忙)或者你的应用程序一直睡眠到其它地方(锁,或者文件I/O等等),你的应用程序获得不到合适的调度时间,造成你的程序没法真正的执行close操作。

1.1.2. 处理方法

服务器保持了大量TIME_WAIT状态

这种情况比较常见,一些爬虫服务器或者WEB服务器上经常会遇到这个问题。

TIME_WAIT是主动关闭连接的一方保持的状态,对于爬虫服务器来说他本身就是“客户端”,在完成一个任务之后,他就会发起主动关闭连接,从而进入TIME_WAIT的状态,然后在保持这个状态2MSL(max segment lifetime)时间之后,彻底关闭回收资源。

这样做的主要出于以下两个方面的考虑:

  • 防止上一次连接中的包,迷路后重新出现,影响新连接(经过2MSL,上一次连接中所有的重复包都会消失),可靠的关闭TCP连接。在主动关闭方发送的最后一个 ack(fin) ,有可能丢失,这时被动方会重新发fin, 如果这时主动方处于 CLOSED 状态 ,就会响应 rst 而不是 ack。所以主动方要处于 TIME_WAIT 状态,而不能是 CLOSED 。另外这么设计TIME_WAIT 会定时的回收资源,并不会占用很大资源的,除非短时间内接受大量请求或者受到攻击。
  • 基于TCP的HTTP协议,关闭TCP连接的是Server端,这样,Server端会进入TIME_WAIT状态,可 想而知,对于访 问量大的Web Server,会存在大量的TIME_WAIT状态,假如server一秒钟接收1000个请求,那么就会积压 240*1000=240,000个 TIME_WAIT的记录,维护这些状态给Server带来负担。当然现代操作系统都会用快速的查找算法来管理这些 TIME_WAIT,所以对于新的 TCP连接请求,判断是否hit中一个TIME_WAIT不会太费时间,但是有这么多状态要维护总是不好。

主动方要处于 TIME_WAIT 状态,而不能是 CLOSED 。另外这么设计TIME_WAIT 会定时的回收资源,并不会占用很大资源的,除非短时间内接受大量请求或者受到攻击。

1.1.3. 解决方案

  • 通过修改/etc/sysctl.conf文件,服务器能够快速回收和重用那些TIME_WAIT的资源。
#表示开启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,表示关闭,该参数强烈不建议开启,会影响nat下的tcp链接    
#net.ipv4.tcp_tw_recycle = 0  
#表示如果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2状态的时间    
net.ipv4.tcp_fin_timeout=30

  

net.ipv4.tcp_tw_recycle参数说明
1. TCP有一种行为,可以缓存每个连接最新的时间戳,后续请求中如果时间戳小于缓存的时间戳,即视为无效,相应的数据包会被丢弃。

Linux是否启用这种行为取决于tcp_timestamps和tcp_tw_recycle,因为tcp_timestamps缺省就是开启的,所以当tcp_tw_recycle被开启后,实际上这种行为就被激活了,当客户端或服务端以NAT方式构建的时候就可能出现问题,下面以客户端NAT为例来说明:

当多个客户端通过NAT方式联网并与服务端交互时,服务端看到的是同一个IP,也就是说对服务端而言这些客户端实际上等同于一个,可惜由于这些客户端的时间戳可能存在差异,于是乎从服务端的视角看,便可能出现时间戳错乱的现象,进而直接导致时间戳小的数据包被丢弃。如果发生了此类问题,具体的表现通常是是客户端明明发送的SYN,但服务端就是不响应ACK。

2. 简单来说,tcp_tw_recycle  机制允许协议不需要真的等待2个最大段生存时间MSL 那么长,就可以关闭一个连接了,只需要等待2个数据包来回时间,这个相对很短,所以TIME_WAIT状态的连接就可以及时回收了,免得占用系统资源。但2*MSL改为了2*RTT, 那么问题很明显,可能出现数据包错乱,比如被动关闭一方的FIN迟迟没有到来,服务器这边会回收这个连接,然后之后的新连接可能就会复用了这个端口port信息,然后突然之间客户端的老的FIN到达了服务器,然后服务器以为这个FIN包对应的Port正好是刚刚建立的新连接的一个FIN包,于是服务器就把新连接给干掉了·····

3. 在4.12之后的内核已移除tcp_tw_recycle内核参数  

服务器保持了大量的close_wait状态

time_wait问题可以通过调整内核参数和适当的设置web服务器的keep-Alive值来解决。因为time_wait是自己可控的,要么就是对方连接的异常,要么就是自己没有快速的回收资源,总之不是由于自己程序错误引起的。但是close_wait就不一样了,服务器保持大量的close_wait只有一种情况,那就是对方发送一个FIN后,程序自己这边没有进一步发送ACK以确认。换句话说就是在对方关闭连接后,程序里没有检测到,或者程序里本身就已经忘了这个时候需要关闭连接,于是这个资源就一直被程序占用着。

1.1.4. 解决方案

  • 关闭正在运行的程序,这个需要视业务情况而定。
  • 尽快地修改程序里的bug,然后提交到线上服务器。
原文地址:https://www.cnblogs.com/jiaojiner/p/15432633.html