昨天解決了一個HttpClient調用錯誤導致的伺服器異常,具體過程如下:
http://blog.csdn.net/shootyou/article/details/6615051
裡頭的分析過程有提到,通過查看伺服器網路狀態檢測到伺服器有大量的CLOSE_WAIT的狀態。
在伺服器的日常維護過程中,會經常用到下面的命令:
[plain] view plain copy print ? netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
它會顯樣本如下面的資訊:
TIME_WAIT 814
CLOSE_WAIT 1
FIN_WAIT1 1
ESTABLISHED 634
SYN_RECV 2
LAST_ACK 1
常用的三個狀態是:ESTABLISHED 表示正在通訊,TIME_WAIT 表示主動關閉,CLOSE_WAIT 表示被動關閉。
具體每種狀態什麼意思,其實無需多說,看看下面這種圖就明白了,注意這裡提到的伺服器應該是業務請求接受處理的一方:
串連關閉狀態轉換描述:
1. 預設情況是established狀態,當伺服器首先向客戶機發送FIN包,然後伺服器進入FIN_WAIT_1狀態。
2. 客戶機向伺服器確認FIN包收到,向伺服器發送FIN/ACK,客戶機進入CLOSE_WAIT狀態。
3. 伺服器收到來自客戶機的FIN/ACK後,進入FIN_WAIT_2狀態
4. 現在客戶機進入被動關閉(“passive close”)狀態,客戶機作業系統等待他上面的應用程式關閉串連。一旦串連被關閉,用戶端會發送FIN包到伺服器
5. 當伺服器收到FIN包後,伺服器會向客戶機發送FIN/ACK確認,然後進入著名的TIME_WAIT狀態。由於在串連關閉後,還不能確定所有串連關閉前的包都被伺服器接受到了(包的接受是沒有先後順序的),因此有了TIME_WAIT狀態。在這個狀態中,伺服器仍然在等待客戶機發送的但是還未到達伺服器的包。這個狀態將保持2*MSL的時間,這裡的MSL指的是一個TCP包在網路中存在的最長時間。一般情況下 2*MSL=240秒。
其實fin_wait1、fin_wati2、close_wait都不是正常現象,只有established和time_wait是正常的。
如果發現fin_wait狀態很多,並且client ip分布正常,那可能是有ddos攻擊、又或者最近的程式改動引起了問題。一般說來後者可能性更大,應該主動聯絡程式員解決。
這麼多狀態不用都記住,只要瞭解到我上面提到的最常見的三種狀態的意義就可以了。一般不到萬不得已的情況也不會去查看網路狀態,如果伺服器出了異常,百分之八九十都是下面兩種情況:
1.伺服器保持了大量TIME_WAIT狀態
2.伺服器保持了大量CLOSE_WAIT狀態
因為linux分配給一個使用者的檔案控制代碼是有限的(可以參考:http://blog.csdn.net/shootyou/article/details/6579139),而TIME_WAIT和CLOSE_WAIT兩種狀態如果一直被保持,那麼意味著對應數目的通道就一直被佔著,而且是“佔著茅坑不使勁”,一旦達到控制代碼數上限,新的請求就無法被處理了,接著就是大量Too Many Open Files異常,tomcat崩潰。。。
下面來討論下這兩種情況的處理方法。
網上有很多資料把這兩種情況的處理方法混為一談,以為最佳化系統核心參數就可以解決問題,其實是不恰當的,最佳化系統核心參數解決TIME_WAIT可能很容易,但是應對CLOSE_WAIT的情況還是需要從程式本身出發。現在來分別說說這兩種情況的處理方法:
1.伺服器保持了大量TIME_WAIT狀態
這種情況比較常見,一些爬蟲伺服器或者WEB伺服器(如果網管在安裝的時候沒有做核心參數最佳化的話)上經常會遇到這個問題,這個問題是怎麼產生的呢。
從上面的示意圖可以看得出來,TIME_WAIT是主動關閉串連的一方保持的狀態,對於爬蟲伺服器來說他本身就是“用戶端”,在完成一個爬取任務之後,他就會發起主動關閉串連,從而進入TIME_WAIT的狀態,然後在保持這個狀態2MSL(max segment lifetime)時間之後,徹底關閉回收資源。為什麼要這麼做。明明就已經主動關閉串連了為啥還要保持資源一段時間呢。這個是TCP/IP的設計者規定的,主要出於以下兩個方面的考慮:
1.防止上一次串連中的包,迷路後重新出現,影響新串連(經過2MSL,上一次串連中所有的重複包都會消失)
2.可靠的關閉TCP串連。在主動關閉方發送的最後一個 ack(fin) ,有可能丟失,這時被動方會重新發fin, 如果這時主動方處於 CLOSED 狀態 ,就會響應 rst 而不是 ack。所以主動方要處於 TIME_WAIT 狀態,而不能是 CLOSED 。另外這麼設計TIME_WAIT 會定時的回收資源,並不會佔用很大資源的,除非短時間內接受大量請求或者受到攻擊。
關於MSL引用下面一段話:
MSL 為一個 TCP Segment (某一塊 TCP 網路封包) 從來源送到目的之間可續存的時間 (也就是一個網路封包在網路上傳輸時能存活的時間),由於 RFC 793 TCP 傳輸協定是在 1981 年定義的,當時的網路速度不像現在的網際網路那樣發達,你可以想像你從瀏覽器輸入網址等到第一個 byte 出現要等 4 分鐘嗎。在現在的網路環境下幾乎不可能有這種事情發生,因此我們大可將 TIME_WAIT 狀態的續存時間大幅調低,好讓 連線埠 (Ports) 能更快空出來給其他連線使用。
再引用網路上的一段話:
值得一說的是,對於基於TCP的HTTP協議,關閉TCP串連的是Server端,這樣,Server端會進入TIME_WAIT狀態。可 想而知,對於訪問量大的Web Server,會存在大量的TIME_WAIT狀態,假如server一秒鐘接收1000個請求,那麼就會積壓240*1000=240,000個 TIME_WAIT的記錄,維護這些狀態給Server帶來負擔。當然現代作業系統都會用快速的尋找演算法來管理這些TIME_WAIT,所以對於新的 TCP串連請求,判斷是否hit中一個TIME_WAIT不會太費時間,但是有這麼多狀態要維護總是不好。
HTTP協議1.1版規定default行為是Keep-Alive,也就是會重用TCP串連傳輸多個 request/response,一個主要原因就是發現了這個問題。
也就是說HTTP的互動跟上面畫的那個圖是不一樣的,關閉串連的不是用戶端,而是伺服器,所以web伺服器也是會出現大量的TIME_WAIT的情況的。
現在來說如何來解決這個問題。
解決思路很簡單,就是讓伺服器能夠快速回收和重用那些TIME_WAIT的資源。
下面來看一下我們網管對/etc/sysctl.conf檔案的修改:
#對於一個建立串連,核心要發送多少個 SYN 串連請求才決定放棄,不應該大於255,預設值是5,對應於180秒左右時間
net.ipv4.tcp_syn_retries=2
#SYN-ACK發送次數
#net.ipv4.tcp_synack_retries=2
#表示當keepalive起用的時候,TCP發送keepalive訊息的頻度。預設是2小時,改為300秒
net.ipv4.tcp_keepalive_time=1200
#在近端丟棄TCP串連之前﹐要進行多少次重試。預設值是7個﹐相當於 50秒 - 16分鐘﹐ #視 RTO 而定。 如果您的系統是負載很大的web伺服器﹐那麼也許需要降低該值﹐ #這類 sockets 可能會耗費大量的資源。 #另外參的考 tcp_max_orphans 。(事實上做NAT的時候,降低該值也是好處顯著的,我本人的網路環境中降低該值為3) #sysctl_tcp_orphan_retries 主要是針對孤立的socket(也就是已經從進程上下文中刪除了, #可是還有一些清理工作沒有完成).對於這種socket,我們重試的最大的次數就是它。
#預設值是7,設定為0表示不重試 # 最近測試發現socket會永遠停留在FIN_WAIT_1這個狀態,而不會重傳逾時RESET後closed。 #在Linux上,tcp_orphan_retries設定為0就是這個效果。
#查了資料,有這一段解釋
/* Do not allow orphaned sockets to eat all our resources.
* This is direct violation of TCP specs, but it is required
* to prevent DoS attacks. It is called when a retransmission timeout
* or zero probe timeout occurs on orphaned socket.
*
* Criteria is still not confirmed experimentally and may change.
* We kill the socket, if:
* 1. If number of orphaned sockets exceeds an administratively configured
* limit.
* 2. If we have strong memory pressure.
*/
set to 0,It doesn't mean "try forever", it means "don't try at all." This is the server trying to politely tell the client that the server is getting ready to close his socket, and if it would please do an orderly disconnect, or send some more data, that would be wonderful. It will try X times to get the client to respond, and after X, it reclaims the socket on the system side.
Setting that number to 0 would suggest to me that that server is heavily utilized, with a zero tolerance policy for orphans. It may also have been a response to a DDOS: lot of DDOS' work by opening a socket connection and then hanging on to it, doing nothing.
net.ipv4.tcp_orphan_retries=3
#表示如果通訊端由本端要求關閉,這個參數決定了它保持在FIN-WAIT-2狀態的時間. #注意,tcp_fin_timeout 不是2MSL,而是Fin-WAIT-2狀態
net.ipv4.tcp_fin_timeout=30
#表示SYN隊列的長度(半串連隊列長度,半串連即接收了SYN,發送了SYN-ACK,但是還沒有收到用戶端ACK的串連), #預設為1024,加大隊列長度為8192,可以容納更多等待串連的網路連接數。
net.ipv4.tcp_max_syn_backlog = 4096
#表示開啟SYN Cookies。當出現SYN等待隊列溢出時,啟用cookies來處理,可防範少量SYN攻擊,預設為0,表示關閉
net.ipv4.tcp_syncookies = 1
#表示開啟重用。允許將TIME-WAIT sockets重新用於新的TCP串連,預設為0,表示關閉
net.ipv4.tcp_tw_reuse = 1
#表示開啟TCP串連中TIME-WAIT sockets的快速回收,預設為0,表示關閉
net.ipv4.tcp_tw_recycle = 1
##減少逾時前的探測次數
net.ipv4.tcp_keepalive_probes=5
##最佳化網路裝置接收隊列,number of unprocessed input packets before kernel starts dropping them, ##default 300。 ##我所理解的含義,每個網路介面接收資料包的速率比核心處理這些包的速率快時,允許送到隊列的最大數目, ##一旦超過將被丟棄。
net.core.netdev_max_backlog=3000
修改完之後執行/sbin/sysctl -p讓參數生效。
這裡頭主要注意到的是 net.ipv4.tcp_tw_reuse net.ipv4.tcp_tw_recycle
net.ipv4.tcp_fin_timeout
net.ipv4.tcp_keepalive_*
這幾個參數。
net.ipv4.tcp_tw_reuse和net.ipv4.tcp_tw_recycle的開啟都是為了回收處於TIME_WAIT狀態的資源。
net.ipv4.tcp_fin_timeout這個時間可以減少在異常情況下伺服器從FIN-WAIT-2轉到TIME_WAIT的時間。
net.ipv4.tcp_keepalive_*一系列參數,是用來設定伺服器檢測串連存活的相關配置。
關於keepalive的用途可以參考:http://hi.baidu.com/tantea/blog/item/580b9d0218f981793812bb7b.html
[2015.01.13更新] 注意tcp_tw_recycle開啟的風險:http://blog.csdn.net/wireless_tech/article/details/6405755
2.伺服器保持了大量CLOSE_WAIT狀態 休息一下,喘口氣,一開始只是打算說說TIME_WAIT和CLOSE_WAIT的區別,沒想到越挖越深,這也是寫部落格總結的好處,總可以有意外的收穫。
TIME_WAIT狀態可以通過最佳化伺服器參數得到解決,因為發生TIME_WAIT的情況是伺服器自己可控的,要麼就是對方串連的異常,要麼就是自己沒有迅速回收資源,總之不是由於自己程式錯誤導致的。 但是CLOSE_WAIT就不一樣了,從上面的圖可以看出來,如果一直保持在CLOSE_WAIT狀態,那麼只有一種情況,就是在對方關閉串連之後伺服器程式自己沒有進一步發出ack訊號。換句話說,就是在對方串連關閉之後,程式裡沒有檢測到,或者程式壓根就忘記了這個時候需要關閉串連,於是這個資源就一直被程式佔著。個人覺得這種情況,通過伺服器核心參數也沒辦法解決,伺服器對於程式搶佔的資源沒有主動回收的權利,除非終止程式運行。
如果你使用的是HttpClient並且你遇到了大量CLOSE_WAIT的情況,那麼這篇日誌也許對你有用: http://blog.csdn.net/shootyou/article/details/6615051 在那邊日誌裡頭我舉了個情境,來說明CLOSE_WAIT和TIME_WAIT的區別,這裡重新描述一下: 伺服器A是一台爬蟲伺服器,它使用簡單的HttpClient去請求資原始伺服器B上面的apache擷取檔案資源,正常情況下,如果請求成功,那麼在抓取完資源後,伺服器A會主動發出關閉串連的請求,這個時候就是主動關閉串連,伺服器A的串連狀態我們可以看到是TIME_WAIT。如果一旦發生異常呢。假設請求的資原始伺服器B上並不存在,那麼這個時候就會由伺服器B發出關閉串連的請求,伺服器A就是被動的關閉了串連,如果伺服器A被動關閉串連之後程式員忘了讓HttpClient釋放串連,那就會造成CLOSE_WAIT的狀態了。
所以如果將大量CLOSE_WAIT的解決辦法總結為一句話那就是:查代碼。因為問題出在伺服器程式裡頭啊。
參考資料: 1.windows下的TIME_WAIT的處理可以參加這位大俠的日誌:http://blog.miniasp.com/post/2010/11/17/How-to-deal-with-TIME_WAIT-problem-under-Windows.aspx
2.WebSphere的伺服器最佳化有一定參考價值: http://publib.boulder.ibm.com/infocenter/wasinfo/v6r0/index.jsp?topic=/com.ibm.websphere.express.doc/info/exp/ae/tprf_tunelinux.html 3.各種核心參數的含義: http://haka.sharera.com/blog/BlogTopic/32309.htm