当前位置: 首页 > Linux

服务器TIME_WAIT和CLOSE_WAIT分析及解决方法

时间:2023-04-06 18:28:15 Linux

查看TIME_WAIT和CLOSE_WAIT命令:netstat-n|awk'/^tcp/{++S[$NF]}END{for(ainS)printa,S[a]}'会显示如下信息,例如:TIME_WAIT,CLOSE_WAIT,FIN_WAIT1,ESTABLISHED,SYN_RECV,LAST_ACK常用的三种状态是:ESTABLISHED表示通信,TIME_WAIT表示主动关闭,CLOSE_WAIT表示被动关闭。服务器异常发生时间最长的是:服务器保持大量的TIME_WAIT状态。服务器保持大量CLOSE_WAIT状态。我们也知道linux系统中分配给每个用户的文件句柄数量是有限的,如果TIME_WAIT和CLOSE_WAIT这两个状态一直保持,就代表对应的通道数(这里应该理解为socket,一般是一个socket会占用服务器端的一个端口,服务器端最大端口数为65535)一直被占用。一旦达到上限,就无法处理新的请求,随之而来的是大量的TooManyOpenFiles异常,然后是tomcat、nginx、apachecrash。..让我们讨论如何处理这两种状态。网上也有很多资料混淆了这两种情况,以为优化内核参数就可以解决。其实,这是不合适的。优化内核参数可以在一定程度上解决time_wait过多的问题,但是处理close_wait还得从应用本身入手。服务器维护大量time_wait状态是很常见的。一般出现在爬虫服务器和web服务器上(如果内核参数没有优化),那么这个问题是怎么产生的呢?从上图可以看出time_wait是主动关闭连接的一方保持的状态。对于爬虫服务器来说,它本身就是一个客户端。完成一次爬取任务后,会发起主动关闭连接,从而进入time_wait状态,然后在这个状态维持2MSL时间后,资源的回收完全关闭。为什么资源2MSL时间保留在这里?这也是TCP/IP的设计者指定的。TCP必须确保所有数据在所有可能的情况下都能正确传递。当你关闭一个socket时,主动关闭端的socket会进入TIME_WAIT状态,而被动关闭端会进入CLOSED状态,这样确实可以保证所有数据都传输完。当一个socket关闭时,是通过两端之间的四次握手来完成的。当一端调用close()时,表示该端没有数据发送。好像握手完成后,socket可以处于初始的CLOSED状态,其实不然。原因是这样安排状态有两个问题。首先,我们没有任何机制保证最后一个ACK能够正常发送。第二,网络上可能还有残留的数据包(游荡的重复数据),我们必须能够正常处理它们。TIMEWAIT就是为解决这两个问题而诞生的。假设最后一个ACK丢失,被动关闭方如果没有收到最后一个ACK,就会重发FIN。此时主动关闭方必须保持一个有效的(保持在time_wait状态)状态信息,这样ACK才能重发。如果主动关闭的socket不保持这个状态而是进入close状态,那么主动关闭的一方在收到被动关闭的一方重新发送的FIN时,会向被动的一方响应一个RST。被动方收到这个RST后,会认为回复错误。因此,TCP要想完成必要的操作,终止双方之间的数据流传输,就必须完整、正确、无损失地传输四次握手的四个步骤。这是socket在关闭后还处于time_wait状态的第一个原因。因为他要等待可能的错误(被动关闭端没有收到最后一个ACK)才能重发ACK。假设当前连接的通信双方都调用close(),双方同时进入closed终端状态,而不是进入time_wait状态。会出现如下问题:如果现在建立新的连接,使用的IP地址和之前的端口完全一样,现在建立的连接是对之前连接的完全复用。我们还假设之前的连接还有数据报留在网络中,在这种情况下,当前连接接收到的数据可能就是之前连接的报文。为了防止这种情况。TCP不允许新连接重用处于time_wait状态的套接字。time_wait状态的socket等待2MSL时间(之所以是MSL的两倍是因为MSL是一个数据报在网络中单方向发送到被认为丢失的时间,即最长存活时间(MaximumSegmentLifetime)报文中,数据报在传输或响应过程中可能成为残留数据报,确认数据报的丢弃及其响应需要两倍的MSL),转为关闭状态。这意味着成功建立的连接必须导致先前网络中剩余的数据报丢失。再次引用网上的一段话:值得一提的是,基于TCP的HTTP协议是通用的(这里为什么说通用,因为当你在keepalive时间内主动关闭到服务器的连接,那么主动关闭端就是client端,否则client是被动关闭端。下面的爬虫例子就是这种情况)server端主动关闭tcp端,这样server端就会进入time_wait状态。可以想象,对于一个访问量很大的web服务器,会存在大量的time_wait状态。如果服务器每秒收到1000个请求,就会积压240*1000=240000个time_wait状态。(RFC793规定MSL为2分钟,实际应用中常用30秒、1分钟、2分钟),维护这些状态给服务器带来了巨大的负担。当然,现代操作系统会使用快速搜索算法来管理这些TIME_WAIT,所以对于新的TCP连接请求,判断命中中是否有TIME_WAIT不会花太多时间,但是有那么多总是不好的状态保持。HTTP协议1.1版本规定默认行为是keep-Alive,即会重用tcp连接来传输多个request/response。这样做的主要原因是为了发现我们上面提到的问题。服务器保持大量close_wait状态time_wait问题可以通过调整内核参数和适当设置web服务器的keep-alive值来解决。因为time_wait是自己可控的,要么是对方连接异常,要么是没有快速回收资源,总之不是程序错误导致的。但是close_wait不同。从上图我们可以看出,只有一种情况是服务端保持了大量的close_wait,即对方发送了一个FIN后,程序本身并没有再发送ACK进行确认。也就是说,对方关闭连接后,程序没有检测到,或者程序自己忘记了此时需要关闭连接,所以这个资源已经被程序占用了。这时候快速的解决办法是:关闭正在运行的程序,这个要看业务情况。尽快修改程序中的bug,然后提交测试到在线服务器。注意:在写这篇文章之前,我并没有完全理解我在工作中遇到的一个问题。程序员写了一个爬虫(php)运行在采集服务器A上,程序到B服务器上去采集资源,但是A服务器很快发现有大量的连接处于close_wait状态。后来人工检查发现这些处于close_wait状态的请求结果都是404,也就是说B服务器上没有可以请求的资源。下面引用网友分析的结论:服务器A是一个爬虫服务器,它通过一个简单的HttpClient请求资源服务器B上的apache获取文件资源。它会主动发出关闭连接的请求。这时,它会主动关闭连接。我们可以看到服务器A的连接状态是TIME_WAIT。如果发生异常怎么办?假设请求的资源在服务器B上不存在,那么此时服务器B会发出关闭连接的请求,服务器A会被动关闭连接。如果程序员在服务器A被动关闭连接后忘记让HttpClient释放连接,就会导致CLOSE_WAIT的状态。