关键词:socket,tcp三次握手,tcp四次握手,2MSL最大报文生存时间,LVS,负载均衡前端
新年上班第一天,忽然遇到一个socket链接No buffer space available的问题,致使接口大面积调用(webservice,httpclient)失败的问题,重启服务器后又恢复了正常。java
具体异常栈信息以下:web
Caused by: java.net.SocketException: No buffer space available (maximum connections reached?): connect at org.apache.axis.AxisFault.makeFault(AxisFault.java:101) at org.apache.axis.transport.http.HTTPSender.invoke(HTTPSender.java:154) at org.apache.axis.strategies.InvocationStrategy.visit(InvocationStrategy.java:32) at org.apache.axis.SimpleChain.doVisiting(SimpleChain.java:118) at org.apache.axis.SimpleChain.invoke(SimpleChain.java:83) at org.apache.axis.client.AxisClient.invoke(AxisClient.java:165) at org.apache.axis.client.Call.invokeEngine(Call.java:2784) at org.apache.axis.client.Call.invoke(Call.java:2767) at org.apache.axis.client.Call.invoke(Call.java:2443) at org.apache.axis.client.Call.invoke(Call.java:2366) at org.apache.axis.client.Call.invoke(Call.java:1812) Caused by: java.net.SocketException: No buffer space available (maximum connections reached?): connect at java.net.PlainSocketImpl.socketConnect(Native Method) at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:333) at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:195) at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:182) at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:366) at java.net.Socket.connect(Socket.java:519) at sun.reflect.GeneratedMethodAccessor24.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.apache.axis.components.net.DefaultSocketFactory.create(DefaultSocketFactory.java:153) at org.apache.axis.components.net.DefaultSocketFactory.create(DefaultSocketFactory.java:120) at org.apache.axis.transport.http.HTTPSender.getSocket(HTTPSender.java:191) at org.apache.axis.transport.http.HTTPSender.writeToSocket(HTTPSender.java:404) at org.apache.axis.transport.http.HTTPSender.invoke(HTTPSender.java:138)
查阅了网上的资料,基本能够把问题锁定在:系统并发过大,链接数过多,部分socket链接没法释放关闭,而持续请求又致使没法释放的socket链接不断积压,最终致使No buffer space available。算法
最快的解决办法:重启服务器,注意,重启tomcat不起做用。下面将分析最终的解决办法。apache
虽然重启服务器能最快的将socket链接释放,可是问题很容易复现,很明显这不是问题的根本解决方式。还有几个问题须要进行进一步分析:windows
l 打开cmd输入netstat -an,发现存在大量处于TIME_WAIT状态的TCP链接,也就是以前提到的未释放的socket链接,而且server端口在不断变化,这又是什么现象呢?以下如图后端
l 系统是否有自动关闭链接的措施,是代码问题仍是性能问题?tomcat
下面咱们来分析解决这几个问题。安全
咱们知道,TCP关闭链接须要通过四次握手,为何是四次握手,而不是像创建链接那样三次握手,看看下面三次握手和四次握手的流程图。服务器
三次握手创建链接示意图
四次握手关闭链接示意图
从上面的三次握手创建链接示意图中能够知道,只要client端和server端都接收到了对方发送的ACK应答以后,双方就能够创建链接,以后就能够进行数据交互了,这个过程须要三步。
而四次握手关闭链接示意图中,TCP协议中,关闭TCP链接的是Server端(固然,关闭均可以由任意一方发起),当Server端发起关闭链接请求时,向Client端发送一个FIN报文,Client端收到FIN报文时,极可能还有数据须要发送,因此并不会当即关闭SOCKET,因此先回复一个ACK报文,告诉Server端,“你发的FIN报文我收到了”。当Client端的全部报文都发送完毕以后,Client端向Server端发送一个FIN报文,此时Client端进入关闭状态,不在发送数据。
Server端收到FIN报文后,就知道能够关闭链接了,可是网络是不可靠的,Client端并不知道Server端要关闭,因此Server端发送ACK后进入TIME_WAIT状态,若是Client端没有收到ACK则Server段能够从新发送。Client端收到ACK后,就知道能够断开链接了。Server端等待了2MSL(Max Segment Lifetime,最大报文生存时间)后依然没有收到回复,则证实Client端已正常断开,此时,Server端也能够断开链接了。2MSL的TIME_WAIT等待时间就是由此而来。
咱们知道了TIME_WAIT的由来,TIME_WAIT 状态最大保持时间是2 * MSL,在1-4分钟之间,因此当系统并发过大,Client-Server链接数过多,Server端会在1-4分钟以内积累大量处于TIME_WAIT状态的没法释放的socket链接,致使服务器效率急剧降低,甚至耗完服务器的全部资源,最终致使No buffer space available (maximum connections reached?): connect
问题的发生。
对于大型的应用,访问量较高,一台Server每每不能知足服务需求,这时就须要多台Server共同对外提供服务。如何充分、最大的利用多台Server的资源处理请求,这时就须要请求调度,将请求合理均匀的分配到各台Server。
LVS (Linux Virtual Server)集群(Cluster)技术就是实现这一需求的方式之一。采用IP负载均衡技术和基于内容请求分发技术。调度器具备很好的吞吐率,将请求均衡地转移到不一样的服务器上执行,且调度器自动屏蔽掉服务器的故障,从而将一组服务器构成一个高性能的、高可用的虚拟服务器。
LVS集群采用三层结构,其主要组成部分为:
l 负载均衡调度器(load balancer),它是整个集群对外面的前端机,负责将客户的请求发送到一组服务器上执行,而客户认为服务是来自一个IP地址(咱们可称之为虚拟IP地址)上的。
l 服务器池(server pool),是一组真正执行客户请求的服务器,执行的服务有WEB、MAIL、FTP和DNS等。
l 共享存储(shared storage),它为服务器池提供一个共享的存储区,这样很容易使得服务器池拥有相同的内容,提供相同的服务。
其结构以下图所示:
LVS结构示意图
从LVS结构示意图中能够看出,Load Balancer到后端Server的IP的数据包的 源IP地址都是同样(Load Balancer的IP地址和Server 的IP地址属于同一网段),而客户端认为服务是来自一个IP地址(实际上就是Load Balancer的IP),频繁的TCP链接创建和关闭,使得Load Balancer到后端Server的TCP链接会受到限制,致使在server上留下不少处于TIME_WAIT状态的链接,并且这些状态对应的远程IP地址都是Load Balancer的。Load Balancer的端口最多也就60000多个(2^16=65536,1~1023是保留端口,还有一些其余端口缺省也不会用),每一个Load Balancer上的端口一旦进入 Server的TIME_WAIT黑名单,就有240秒不能再用来创建和Server的链接,这样Load Balancer和Server的链接就颇有限。因此咱们看到了使用netstat -an命令查看网络链接情况时同一个 remote IP会有不少端口。
从上面的分析来看,致使出现No buffer space available这一问题的缘由是多方面的,缘由以及解决办法以下:
l 从代码层面上看,webservice或httpclient调用未进行链接释放,致使资源没法回收。
解决办法是在axis2的客户端代码中进行链接关闭,以下:
stub._getServiceClient().cleanupTransport();
stub._getServiceClient().cleanup();
stub.cleanup();
stub = null;
及时的关闭和clean能有效的避免内存溢出的问题,及时回收资源。
或者httpClient中,最终要在finally调用response.close()或者httpPost.releaseConnection() 进行链接释放。
l 从系统层面上看,系统socket链接数设置不合理,socket链接数太小,易达到上限;其次是2MSL设置过长,容易积压TIME_WAIT状态的TCP链接。
解决办法是修改Linux内核参数,
修改系统socket最大链接数,在文件/etc/security/limits.conf最后加入下面两行:
* soft nofile 32768
* hard nofile 32768
或者缩小2MSL的时长、容许重用处于TIME_WAIT状态的TCP链接、快速回收处于 TIME_WAIT状态的TCP链接,修改/etc/sysctl.conf,添加以下几行:
#改系統默认的TIMEOUT时间
net.ipv4.tcp_fin_timeout=2
#启重用,容许将TIME_WAIT sockets从新用于新的TCP链接 默认为0表示关闭
net.ipv4.tcp_tw_reuse=1
#开启TCP链接中TIME_WAIT sockets的快速回收 默认为0 表示关闭
net.ipv4.tcp_tw_recycle=1
对于windows环境,可经过修改注册表进行配置:
\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters
添加一个DWORD类型的值TcpTimedWaitDelay,值能够根据实际状况配置。
\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\TCPIP\Parameters
添加一个DWORD类型的值MaxUserPort ,值能够根据实际状况配置。
上面这些参数根据实际状况进行配置。
l 从LVS 层面上看,调度算法不合理,致使请求过多分配到某一台服务器上。
解决办法,根据实际状况指定合理的负载均衡解决方案。
l 从安全层面上看,当服务器遭到DDoS(拒绝服务攻击)时,服务器大量积压TIME_WAIT状态的TCP链接而没法向外提供服务。
解决办法,增强安全防御。