socket-詳細分析No buffer space available

socket連接No buffer space available的問題,導致接口大面積調用(webservice,httpclient)失敗的問題,重啓服務器後又恢復了正常。

問題詳情

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不起作用。下面將分析最終的解決辦法。

問題分析

雖然重啓服務器能最快的將socket連接釋放,但是問題很容易復現,很明顯這不是問題的根本解決方式。還有幾個問題需要進行進一步分析:

  1. 打開cmd輸入netstat -an,發現存在大量處於TIME_WAIT狀態的TCP連接,也就是之前提到的未釋放的socket連接,並且server端口在不斷變化,這又是什麼現象呢?如下如圖
    這裏寫圖片描述
  2. 系統是否有自動關閉連接的措施,是代碼問題還是性能問題?
    下面我們來分析解決這幾個問題。

    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端沒有收到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結構示意圖
從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這一問題的原因是多方面的,原因以及解決辦法如下:

  1. 從代碼層面上看,webservice或httpclient調用未進行連接釋放,導致資源無法回收。
    解決辦法是在axis2的客戶端代碼中進行連接關閉,如下:
stub._getServiceClient().cleanupTransport();
stub._getServiceClient().cleanup();
stub.cleanup();
stub = null;

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

或者httpClient中,最終要在finally調用response.close()或者httpPost.releaseConnection() 進行連接釋放。
2. 從系統層面上看,系統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 ,值可以根據實際情況配置。

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

  1. 從LVS 層面上看,調度算法不合理,導致請求過多分配到某一臺服務器上。
    解決辦法,根據實際情況指定合理的負載均衡解決方案。
  2. 從安全層面上看,當服務器遭到DDoS(拒絕服務攻擊)時,服務器大量積壓TIME_WAIT狀態的TCP連接而無法向外提供服務。
    解決辦法,加強安全防護。

文章全文轉至 [ITPSC](http://www.cnblogs.com/hjwublog/p/5114380.html)>>>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章