關鍵詞: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鏈接而沒法向外提供服務。
解決辦法,增強安全防禦。