socket-詳細分析No buffer space available

關鍵詞: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

 

下面咱們來分析解決這幾個問題。安全

 

TIME_WAIT狀態的由來

 

咱們知道,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端沒有收到ACKServer能夠從新發送。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集羣採用三層結構,其主要組成部分爲:

負載均衡調度器load balancer),它是整個集羣對外面的前端機,負責將客戶的請求發送到一組服務器上執行,而客戶認爲服務是來自一個IP地址(咱們可稱之爲虛擬IP地址)上的。

服務器池server pool),是一組真正執行客戶請求的服務器,執行的服務有WEBMAILFTPDNS等。

共享存儲shared storage),它爲服務器池提供一個共享的存儲區,這樣很容易使得服務器池擁有相同的內容,提供相同的服務。

其結構以下圖所示:

 

              LVS結構示意圖

 

LVS結構示意圖中能夠看出,Load Balancer到後端ServerIP的數據包的 源IP地址都是同樣Load BalancerIP地址和Server IP地址屬於同一網段),而客戶端認爲服務是來自一個IP地址(實際上就是Load BalancerIP),頻繁的TCP鏈接創建和關閉,使得Load Balancer到後端ServerTCP鏈接會受到限制,致使在server上留下不少處於TIME_WAIT狀態的鏈接,並且這些狀態對應的遠程IP地址都是Load Balancer的。Load Balancer的端口最多也就60000多個(2^16=65536,1~1023是保留端口,還有一些其餘端口缺省也不會用),每一個Load Balancer上的端口一旦進入 ServerTIME_WAIT黑名單,就有240秒不能再用來創建和Server的鏈接,這樣Load Balancer和Server的鏈接就頗有限。因此咱們看到了使用netstat -an命令查看網絡鏈接情況時同一個 remote IP會有不少端口。

最終解決辦法

從上面的分析來看,致使出現No buffer space available這一問題的緣由是多方面的,緣由以及解決辦法以下:

 

從代碼層面上看,webservicehttpclient調用未進行鏈接釋放,致使資源沒法回收

 

解決辦法是在axis2的客戶端代碼中進行鏈接關閉,以下:

stub._getServiceClient().cleanupTransport();
   stub._getServiceClient().cleanup();
    stub.cleanup();
    stub = null;

及時的關閉和clean能有效的避免內存溢出的問題,及時回收資源。

或者httpClient中,最終要在finally調用response.close()或者httpPost.releaseConnection() 進行鏈接釋放。

 

從系統層面上看,系統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的快速回收 默認爲表示關閉
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 ,值能夠根據實際狀況配置。

 

上面這些參數根據實際狀況進行配置。

 

從LVS 層面上看,調度算法不合理,致使請求過多分配到某一臺服務器上

 

解決辦法,根據實際狀況指定合理的負載均衡解決方案。

 

從安全層面上看,當服務器遭到DDoS(拒絕服務攻擊)時,服務器大量積壓TIME_WAIT狀態的TCP鏈接而沒法向外提供服務

 

解決辦法,增強安全防禦。

相關文章
相關標籤/搜索