首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 其他教程 > 操作系统 >

从有关问题看本质: 研究TCP close_wait的内幕

2012-10-06 
从问题看本质: 研究TCP close_wait的内幕/*?* @author: ahuaxuan?* @date: 2010-4-30?*/?最近遇到的一个关

从问题看本质: 研究TCP close_wait的内幕

/*

?* @author: ahuaxuan

?* @date: 2010-4-30

?*/

?

最近遇到的一个关于socket.close的问题,在某个应用服务器出现的状况(执行netstat -np | grep tcp):?

tcp ? ? ? ?0 ? ? ?0 10.224.122.16:50158 ? ? ? ? 10.224.112.58:8788 ? ? ? ? ?CLOSE_WAIT

tcp ? ? ? ?0 ? ? ?0 10.224.122.16:37655 ? ? ? ? 10.224.112.58:8788 ? ? ? ? ?CLOSE_WAIT

tcp ? ? ? ?1 ? ? ?0 127.0.0.1:32713 ? ? ? ? ? ? 127.0.0.1:8080 ? ? ? ? ? ? ?CLOSE_WAIT

tcp ? ? ? 38 ? ? ?0 10.224.122.16:34538 ? ? ? ? 10.224.125.42:443 ? ? ? ? ? CLOSE_WAIT

tcp ? ? ? 38 ? ? ?0 10.224.122.16:33394 ? ? ? ? 10.224.125.42:443 ? ? ? ? ? CLOSE_WAIT

tcp ? ? ? ?1 ? ? ?0 10.224.122.16:18882 ? ? ? ? 10.224.125.10:80 ? ? ? ? ? ?CLOSE_WAIT

tcp ? ? ? ?1 ? ? ?0 10.224.122.16:18637 ? ? ? ? 10.224.125.10:80 ? ? ? ? ? ?CLOSE_WAIT

tcp ? ? ? ?1 ? ? ?0 10.224.122.16:19655 ? ? ? ? 10.224.125.12:80 ? ? ? ? ? ?CLOSE_WAIT

........................................

?

总共出现了200个CLOSE_WAIT的socket.而且这些socket长时间得不到释放.下面我们来看看为什么会出现这种大量socket的CLOSE_WAIT情况

?

首先我们要搞清楚的是,这个socket是谁发起的,我们可以看到122.16这台机器开了很多端口,而且端口号都很大,125.12 或者125.10上的端口都是很常见服务器端口,所以122.16上这么多CLOSE_WAIT

的socket是由122.16开启的,换句话说这台机器是传统的客户端,它会主动的请求其他机器的服务端口.

?

要搞清楚为什么会出现CLOSE_WAIT,那么首先我们必须要清楚CLOSE_WAIT的机制和原理.

?

假设我们有一个client, 一个server.

?

当client主动发起一个socket.close()这个时候对应TCP来说,会发生什么事情呢?如下图所示.

?

?从有关问题看本质: 研究TCP close_wait的内幕

?

?

client首先发送一个FIN信号给server, 这个时候client变成了FIN_WAIT_1的状态, server端收到FIN之后,返回ACK,然后server端的状态变成了CLOSE_WAIT.

接着server端需要发送一个FIN给client,然后server端的状态变成了LAST_ACK,接着client返回一个ACK,然后server端的socket就被成功的关闭了.

?

从这里可以看到,如果由客户端主动关闭一链接,那么客户端是不会出现CLOSE_WAIT状态的.客户端主动关闭链接,那么Server端将会出现CLOSE_WAIT的状态.

而我们的服务器上,是客户端socket出现了CLOSE_WAIT,由此可见这个是由于server主动关闭了server上的socket.

?

那么当server主动发起一个socket.close(),这个时候又发生了一些什么事情呢.

?从有关问题看本质: 研究TCP close_wait的内幕

?

从图中我们可以看到,如果是server主动关闭链接,那么Client则有可能进入CLOSE_WAIT,如果Client不发送FIN包,那么client就一直会处在CLOSE_WAIT状态(后面我们可以看到有参数可以调整这个时间).

?

那么现在我们要搞清楚的是,在第二中场景中,为什么Client不发送FIN包给server.要搞清楚这个问题,我们首先要搞清楚server是怎么发FIN包给client的,其实server就是调用了

socket.close方法而已,也就是说如果要client发送FIN包,那么client就必须调用socket.close,否则就client就一直会处在CLOSE_WAIT(但事实上不同操作系统这点的实现还不一样,

在ahuaxuan(ahuaxuan.iteye.com)的例子中也出现了这样的case).

?

下面我们来做几个实验

实验一:

环境:

服务器端:win7+tomcat,tomcat的keep-alive的时间为默认的15s.

客户端:mac os

实验步骤:服务器启动后,客户端向服务器发送一个get请求,然后客户端阻塞,等待服务器端的socket超时.通过netstat -np tcp可以看到的情况是发送get请求时,服务器和客户端链接是ESTABLISHED, 15s之后,客户端变成了CLOSE_WAIT,而服务器端变成了FIN_WAIT_2.这一点也在我们的预料之中,而这个时候由于客户端线程阻塞,客户端socket空置在那里,不做任何操作,2分钟过后,这个链接不管是在win7上,还是在mac os都看不到了.可见,FIN_WAIT_2或者CLOSE_WAIT有一个timeout.在后面的实验,可以证明,在这个例子中,其实是FIN_WAIT_2有一个超时,一旦过了2分钟,那么win7会发一个RST给mac os要求关闭双方的socket.

?

实验二

服务器端:ubuntu9.10+tomcat,tomcat的keep-alive的时间为默认的15s.

客户端:mac os

实验步骤:服务器启动后,客户端向服务器发送一个get请求,然后客户端阻塞,等待服务器端的socket超时.通过netstat -np tcp(ubuntu使用netstat -np|grep tcp)可以看到的情况是发送get请求时,服务器和客户端链接是ESTABLISHED, 15s之后,客户端变成了CLOSE_WAIT,而服务器端变成了FIN_WAIT_2.这一点也也在我们的预料之中,而这个时候由于客户端线程阻塞,客户端socket空置在那里,不做任何操作,1分钟过后,ubuntu上的那个socket不见了,但是mac os上的socket还在,而且还是CLOSE_WAIT,这说明,FIN_WAIT_2确实有一个超时时间,win7上的超时操作可以关闭mac os上的socket,而ubuntu上的FIN_WAIT_2超时操作却不能关闭mac os上的socket(其状一直是CLOSE_WAIT).

?

实验三

服务器端:mac os+tomcat,tomcat的keep-alive的时间为默认的15s.

客户端:mac os

实验步骤:服务器启动后,客户端向服务器发送一个get请求,然后客户端阻塞,等待服务器端的socket超时.通过netstat -np tcp可以看到的情况是发送get请求时,服务器和客户端链接是ESTABLISHED, 15s之后,客户端变成了CLOSE_WAIT,而服务器端变成了FIN_WAIT_2.这一点也在我们的预料之中,而这个时候由于客户端线程阻塞,客户端socket空置在那里,不做任何操作,4分钟过后,mac os服务器端上的那个socket不见了,但是mac os客户端上的socket还在,而且还是CLOSE_WAIT,这说明,FIN_WAIT_2确实有一个超时时间,win7上的超时操作可以关闭mac os上的socket,而ubuntu和mac os上的FIN_WAIT_2超时操作却不能关闭mac os上的socket.

?

?

?

总结, 当服务器的内核不一样上FIN_WAIT_2的超时时间和操作是不一样的.

经查:控制FIN_WAIT_2的参数为:

/proc/sys/net/ipv4/tcp_fin_timeout

如 果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2状态的时间。对端可以出错并永远不关闭连接,甚至意外当机。缺省值是60秒。2.2 内核的通常值是180秒,你可以按这个设置,但要记住的是,即使你的机器是一个轻载的WEB服务器,也有因为大量的死套接字而内存溢出的风险,FIN- WAIT-2的危险性比FIN-WAIT-1要小,因为它最多只能吃掉1.5K内存,但是它们的生存期长些。参见tcp_max_orphans。

?

实验四

服务器端:ubuntu9.10+tomcat,tomcat的keep-alive的时间为默认的15s.

客户端:mac os

实验步骤:服务器启动后,客户端向服务器发送一个get请求,然后关闭客户端关闭socket.通过netstat -np tcp可以看到的情况是发送get请求时,服务器和客户端链接是ESTABLISHED, 客户端拿到数据之后,客户端变成了TIME_WAIT,而服务器端变成了已经看不到这个socket了.这一点也也在我们的预料之中,谁主动关闭链接,那么谁就需要进入TIME_WAIT状态(除非他的FIN_WAIT_2超时了),大约1分钟之后这个socket在客户端也消失了.

?

实验证明TIME_WAIT的状态会存在一段时间,而且在这个时间端里,这个FD是不能被回收的.

?

但是我们的问题是客户端有很多CLOSE_WAIT,而且我们的服务器不是windows,而是linux,所以CLOSE_WAIT有没有超时时间呢,肯定有,而且默认情况下这个超时时间应该是比较大的.否则不会一下子看到两百个CLOSE_WAIT的状态.

?

客户端解决方案:

?

1.由于socket.close()会导致FIN信号,而client的socket CLOSE_WAIT就是因为该socket该关的时候,我们没有关,所以我们需要一个线程池来检查空闲连接中哪些进入了超时状态(idleTIME),但进入超时

的socket未必是CLOSE_WAIT的状态的.不过如果我们把空闲超时的socket关闭,那么CLOSE_WAIT的状态就会消失.(问题:像HttpClient这样的工具包中,如果要检查链接池,那么则需要锁定整个池,而这个时候,用户请求获取connection的操作只能等待,在高并发的时候会造成程序响应速度下降,具体参考IdleConnectionTimeoutThread.java(HttpClient3.1))

?

2.经查,其实有参数可以调整CLOSE_WAIT的持续时间,如果我们改变这个时间,那么可以让CLOSE_WAIT只保持很短的时间(当然这个参数不只作用在CLOSE_WAIT上,缩短这个时间可能会带来其他的影响).在客户端机器上修改如下:

sysctl -w net.ipv4.tcp_keepalive_time=60(缺省是2小时,现在改成了60秒)

sysctl -w net.ipv4.tcp_keepalive_probes=2

sysctl -w net.ipv4.tcp_keepalive_intvl=2

我们将CLOSE_WAIT的检查时间设置为30s,这样一个CLOSE_WAIT只会存在30S.

?

3. 当然,最重要的是我们要检查客户端链接的空闲时间,空闲时间可以由客户端自行定义,比如idleTimeout,也可由服务器来决定,服务器只需要每次在response.header中加入一个头信息,比如说名字叫做timeout头,当然一般情况下我们会用keep-alive这个头字段, 如果服务器设置了该字段,那么客户端拿到这个属性之后,就知道自己的connection最大的空闲时间,这样不会由于服务器关闭socket,而导致客户端socket一直close_wait在那里.

?

服务器端解决方案

?

4.前面讲到客户端出现CLOSE_WAIT是由于服务器端Socket的读超时,也是TOMCAT中的keep-alive参数.那么如果我们把这个超时时间设置的长点,会有什么影响?

如果我们的tomcat既服务于浏览器,又服务于其他的APP,而且我们把connection的keep-alive时间设置为10分钟,那么带来的后果是浏览器打开一个页面,然后这个页面一直不关闭,那么服务器上的socket也不能关闭,它所占用的FD也不能服务于其他请求.如果并发一高,很快服务器的资源将会被耗尽.新的请求再也进不来. 那么如果把keep-alive的时间设置的短一点呢,比如15s? 那么其他的APP来访问这个服务器的时候,一旦这个socket, 15s之内没有新的请求,那么客户端APP的socket将出现大量的CLOSE_WAIT状态.

所以如果出现这种情况,建议将你的server分开部署,服务于browser的部署到单独的JVM实例上,保持keep-alive为15s,而服务于架构中其他应用的功能部署到另外的JVM实例中,并且将keep-alive的时间设置的更

长,比如说1个小时.这样客户端APP建立的connection,如果在一个小时之内都没有重用这条connection,那么客户端的socket才会进入CLOSE_WAIT的状态.针对不同的应用场景来设置不同的keep-alive时间,可以帮助我们提高程序的性能.

?

5.如果我们的应用既服务于浏览器,又服务于其他的APP,那么我们还有一个终极解决方案.

那就是配置多个connector, 如下:

<!-- for browser -->

?<Connector port="8080" protocol="HTTP/1.1"?

?? ? ? ? ? ? ? connectionTimeout="20000"?

?? ? ? ? ? ? ? redirectPort="8443" />

?

<!-- for other APP -->

<Connector port="8081" protocol="HTTP/1.1"?

? ? ? ? ? ? ? connectionTimeout="20000"?

? ? ? ? ? ? ? redirectPort="8443" keepAliveTimeout="330000" />

?

访问的时候,浏览器使用8080端口,其他的APP使用8081端口.这样可以保证浏览器请求的socket在15s之内如果没有再次使用,那么tomcat会主动关闭该socket,而其他APP请求的socket在330s之内没有使用,才关闭该socket,这样做可以大大减少其他APP上出现CLOSE_WAIT的几率.

?

你一定会问,如果我不设置keepAliveTimeout又怎么样呢,反正客户端有idleTimeout,客户端的close_wait不会持续太长时间,请注意看上图中标红的地方,一个是close_wait,还有一个是time_wait状态,也就是说谁主动发起请求,那么它将会最终进入time_wait状态,据说windows上这个time_wait将持续4分钟,我在linux上的测试表明,linux上它大概是60s左右,也就是说高并发下,也就是服务器也需要过60s左右才能真正的释放这个FD.所以我们如果提供http服务给其他APP,那么我们最好让客户端优先关闭socket,也就是将客户端的idleTimeout设置的比server的keepalivetimeout小一点.这样保证time_wait出现在客户端. 而不是资源较为紧张的服务器端.

?

总结:

?? ? ? 本文中ahuaxuan给大家揭示了TCP层client和server端socket关闭的一般流程,并且指出异常情况下client和server端各自会发生的情况,包含了在不同平台上出现了的不同情况, 同时说明了在应用层上我们可以做什么样的逻辑来保证socket关闭时对server端带来最小的影响.

?

?

?

下面是网上找到的一些资料:

?

2 楼 moshalanye 2010-09-13   经典!相当不错! ronghua你涉及的面越来越宽了!
3 楼 david.org 2010-11-15   keep alive 和 CLOSE_WAIT 没有任何关系,Server端出现大量CLOSE_WAIT的状态最大的杀手还是自己的程序有问题。

TCP双方有一方未关闭Socket,导致四路握手未完成,不应该依靠修改内核参数来解决这样的问题。 4 楼 dongritengfei 2011-05-28   good,分析得很底层。
关于3楼的说法:对于一个企业级应用,可用性要求是很高的,程序中出错是难免的,没有人可以保证他的程序一点问题都没有。我们应该从多种角度来补救这些错误。如果让你在一个内存是32K的机器上一个写一个网络程序,你就会考虑这些问题了。 5 楼 hobitton 2011-12-29   这个问题的解决方法是有两个方面的,但是最主要的考虑修改确实不应给在修改参数来达到解决,修改参数应该只是一个优化的方案。应该查到程序为什么没有响应FIN而发送ACK给客户端才是根本所在。

热点排行