TIME_WAIT和CLOSE_WAIT 小分享-運維筆記
相信很多運維工程師遇到過這樣一個情形: 使用者反饋網站訪問巨慢, 網路延遲等問題, 然後就迫切地登入伺服器,終端輸入命令"netstat -a | grep TIME_WAIT | wc -l " 檢視一下, 接著發現有幾百甚至幾千個TIME_WAIT 連線數. 頓時慌了~, 接著嘗試如下解決方案:
開啟 sysctl.conf 檔案,修改以下幾個引數: [[email protected] ~]# vim /etc/sysctl.conf net.ipv4.tcp_tw_recycle = 1 net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_timestamps = 1 [[email protected] ~]# sysctl -p 然後被告知提示: 開啟tw_recylce和tw_reuse功能, 一定需要timestamps的支援,而且這些配置一般不建議開啟,但是對解決TIME_WAIT很多的問題,有很好的用處。 果然, 如上修改後, 過了幾分鐘,TIME_WAIT的數量真的降低了,並且後面也沒發現哪個使用者說有問題了. 做到這裡, 相信大多數運維人員想當然地以為問題已經解決了, 但是,要徹底理解並解決這個問題,可能就沒這麼簡單,或者說,要想徹底搞清楚並解決這個問題, 還是有很長的路要走滴!
下面簡單解釋下什麼是TIME-WAIT和CLOSE-WAIT ?
通常來說要想解決問題,就要先理解問題。有時遇到問題,上網百度個解決方案,臨時修復了問題,就以為問題已經不在了, 其實問題不是真的不存在了,而是可能隱藏在更深的地方,只是我們沒有發現,或者以現有自己的的知識水平無法發現而已。總所周知,由於socket是全雙工的工作模式,一個socket的關閉,是需要四次握手來完成的:
1) 主動關閉連線的一方,呼叫close();協議層傳送FIN包 ;
2) 被動關閉的一方收到FIN包後,協議層回覆ACK;然後被動關閉的一方,進入CLOSE_WAIT狀態,主動關閉的一方等待對方關閉,則進入FIN_WAIT_2狀態;此時,主動關閉的一方等待被動關閉一方的應用程式,呼叫close操作 ;
3)
4) 主動關閉的一方收到FIN包,協議層回覆ACK;此時,主動關閉連線的一方,進入TIME_WAIT狀態;而被動關閉的一方,進入CLOSED狀態 ;
5) 等待2MSL時間,主動關閉的一方,結束TIME_WAIT,進入CLOSED狀態 ;
通過上面的一次socket關閉操作,可以得出以下幾點:
1) 主動關閉連線的一方 – 也就是主動呼叫socket的close操作的一方,最終會進入TIME_WAIT狀態 ;
2) 被動關閉連線的一方,有一箇中間狀態,即CLOSE_WAIT,因為協議層在等待上層的應用程式,主動呼叫close操作後才主動關閉這條連線 ;
3) TIME_WAIT會預設等待2MSL時間後,才最終進入CLOSED狀態;
4) 在一個連線沒有進入CLOSED狀態之前,這個連線是不能被重用的!
所以說這裡憑直覺看,TIME_WAIT並不可怕,CLOSE_WAIT才可怕,因為CLOSE_WAIT很多,表示說要麼是你的應用程式寫的有問題,沒有合適的關閉socket;要麼是說,你的伺服器CPU處理不過來(CPU太忙)或者你的應用程式一直睡眠到其它地方(鎖,或者檔案I/O等等),你的應用程式獲得不到合適的排程時間,造成你的程式沒法真正的執行close操作。
那麼這裡又出現兩個問題:
1) 上面提到的連線重用,那連線到底是個什麼概念?
2) 協議層為什麼要設計一個TIME_WAIT狀態?這個狀態為什麼預設等待2MSL時間才會進入CLOSED
先解釋清楚這兩個問題後, 接著再來看開頭提到的/etc/sysctl.conf檔案中那幾個網路配置引數究竟有什麼用,以及TIME_WAIT的後遺症問題。
Socket連線到底是個什麼概念?
socket 其實就是一個五元組,包括:源IP, 源埠, 目的IP, 目的埠, 型別(TCP or UDP) . 這個五元組,即標識了一條可用的連線。 需要注意是是,經常有很多人把一個socket定義成四元組,也就是源IP:源埠+目的IP:目的埠,這個定義是不正確的。
比如說,如果本地出口IP是110.122.144.166,那麼你的瀏覽器在連線某一個Web伺服器,例如百度的時候,這條socket連線的四元組可能就是:[110.122.144.166:45678, tcp, 110.88.92.104:80] , 源IP為你的出口IP地址 110.122.144.166,源埠為隨機埠 45678,目的IP為百度的某一個負載均衡伺服器IP 110.88.92.104,埠為HTTP標準的80埠。
如果這個時候,你再開一個瀏覽器,訪問百度,將會產生一條新的連線:[110.122.144.166:43678, tcp, 110.88.92.104:80] , 這條新的連線的源埠為一個新的隨機埠 43678。如此來看,如果你的本機需要壓測百度,那麼你最多可以建立多少個連線呢?
TIME_WAIT有什麼用?
如果來做個類比的話,TIME_WAIT的出現,對應的是你的程式裡的異常處理,它的出現,就是為了解決網路的丟包和網路不穩定所帶來的其他問題:
1) 防止前一個連線【五元組,這裡繼續以 110.122.144.166:45678, tcp, 110.88.92.104:80 為例】上延遲的資料包或者丟失重傳的資料包,被後面複用的連線【前一個連線關閉後,此時你再次訪問百度,新的連線可能還是由110.122.144.166:45678, tcp, 110.88.92.104:80 這個五元組來表示,也就是源埠湊巧還是45678】錯誤的接收(異常:資料丟了,或者傳輸太慢了),參見下圖:
- SEQ=3的資料包丟失,重傳第一次,沒有得到ACK確認
- 如果沒有TIME_WAIT,或者TIME_WAIT時間非常端,那麼關閉的連線【180.172.35.150:45678, tcp, 180.97.33.108:80 的狀態變為了CLOSED,源埠可被再次利用】,馬上被重用【對180.97.33.108:80新建的連線,複用了之前的隨機埠45678】,並連續傳送SEQ=1,2 的資料包
- 此時,前面的連線上的SEQ=3的資料包再次重傳,同時,seq的序號剛好也是3(這個很重要,不然,SEQ的序號對不上,就會RST掉),此時,前面一個連線上的資料被後面的一個連線錯誤的接收
2) 確保連線方能在時間範圍內,關閉自己的連線。其實,也是因為丟包造成的,參見下圖:
- 主動關閉方關閉了連線,傳送了FIN;
- 被動關閉方回覆ACK同時也執行關閉動作,傳送FIN包;此時,被動關閉的一方進入LAST_ACK狀態;
- 主動關閉的一方回去了ACK,主動關閉一方進入TIME_WAIT狀態;
- 但是最後的ACK丟失,被動關閉的一方還繼續停留在LAST_ACK狀態;
- 此時,如果沒有TIME_WAIT的存在,或者說,停留在TIME_WAIT上的時間很短,則主動關閉的一方很快就進入了CLOSED狀態,也即是說,如果此時新建一個連線,源隨機埠如果被複用,在connect傳送SYN包後,由於被動方仍認為這條連線【五元組】還在等待ACK,但是卻收到了SYN,則被動方會回覆RST;
- 造成主動建立連線的一方,由於收到了RST,則連線無法成功;
所以,這裡看到了,TIME_WAIT的存在是很重要的,如果強制忽略TIME_WAIT,還是有很高的機率,造成資料粗亂,或者短暫性的連線失敗。那麼,為什麼說TIME_WAIT狀態會是持續2MSL(2倍的max segment lifetime)呢?這個時間可以通過修改核心引數調整嗎?第一,這個2MSL,是RFC 793裡定義的,參見RFC的截圖示紅的部分: