TCP短連接產生大量TIME_WAIT導致無法對外建立新TCP連接的原因及解決方法

  1. 實際問題

初步查看發現,無法對外新建TCP連接時,線上服務器存在大量處於TIME_WAIT狀態的TCP連接(最多的一次爲單機10w+,其中引起報警的那個模塊產生的TIME_WAIT約2w),導致其無法跟下游模塊建立新TCP連接。

TIME_WAIT涉及到TCP釋放連接過程中的狀態遷移,也涉及到具體的socket api對TCP狀態的影響,下面開始逐步介紹這些概念。

  1. TCP狀態遷移

面向連接的TCP協議要求每次peer間通信前建立一條TCP連接,該連接可抽象爲一個4元組(four-tuple,有時也稱socket pair):(local_ip, local_port, remote_ip,remote_port),這4個元素唯一地代表一條TCP連接。

1)TCP Connection Establishment

TCP建立連接的過程,通常又叫“三次握手”(three-way handshake),可用下圖來示意:

可對上圖做如下解釋:

a. client向server發送SYN並約定初始包序號(sequence number)爲J;

b. server發送自己的SYN並表明初始包序號爲K,同時,針對client的SYNJ返回ACKJ+1(注:J+1表示server期望的來自該client的下一個包序爲J+1);

c. client收到來自server的SYN+ACK後,發送ACKK+1,至此,TCP建立成功。

其實,在TCP建立時的3次握手過程中,還要通過SYN包商定各自的MSS,timestamp等參數,這涉及到協議的細節,本文旨在拋磚引玉,不再展開。

2)TCPConnection Termination

與建立連接的3次握手相對應,釋放一條TCP連接時,需要經過四步交互(又稱“四次揮手”),如下圖所示:

可對上圖做如下解釋:

a. 連接的某一方先調用close()發起主動關閉(active close),該api會促使TCP傳輸層向remotepeer發送FIN包,該包表明發起active close的application不再發送數據(特別注意:這裏“不再發送數據”的承諾是從應用層角度來看的,在TCP傳輸層,還是要將該application對應的內核tcp send buffer中當前尚未發出的數據發到鏈路上)。

remote peer收到FIN後,需要完成被動關閉(passive close),具體分爲兩步:

b. 首先,在TCP傳輸層,先針對對方的FIN包發出ACK包(主要ACK的包序是在對方FIN包序基礎上加1);

c. 接着,應用層的application收到對方的EOF(end-of-file,對方的FIN包作爲EOF傳給應用層的application)後,得知這條連接不會再有來自對方的數據,於是也調用close()關閉連接,該close會促使TCP傳輸層發送FIN。

d. 發起主動關閉的peer收到remote peer的FIN後,發送ACK包,至此,TCP連接關閉。

注意1:TCP連接的任一方均可以首先調用close()以發起主動關閉,上圖以client主動發起關閉做說明,而不是說只能client發起主動關閉。

注意2:上面給出的TCP建立/釋放連接的過程描述中,未考慮由於各種原因引起的重傳、擁塞控制等協議細節,感興趣的同學可以查看各種TCP RFC Documents ,比如TCP RFC793。

3)TCP StateTransition Diagram

上面介紹了TCP建立、釋放連接的過程,此處對TCP狀態機的遷移過程做總體說明。將TCP RFC793中描述的TCP狀態機遷移圖摘出如下(下圖引用自這裏):

TCP狀態機共含11個狀態,狀態間在各種socket apis的驅動下進行遷移,雖然此圖看起來錯綜複雜,但對於有一定TCP網絡編程經驗的同學來說,理解起來還是比較容易的。限於篇幅,本文不準備展開詳述,想了解具體遷移過程的新手同學,建議閱讀《Linux Network Programming Volume1》第2.6節。

  1. TIME_WAIT狀態

經過前面的鋪墊,終於要講到與本文主題相關的內容了。 _

從TCP狀態遷移圖可知,只有首先調用close()發起主動關閉的一方纔會進入TIME_WAIT狀態,而且是必須進入(圖中左下角所示的3條狀態遷移線最終均要進入該狀態才能回到初始的CLOSED狀態)。

從圖中還可看到,進入TIME_WAIT狀態的TCP連接需要經過2MSL才能回到初始狀態,其中,MSL是指Max

Segment Lifetime,即數據包在網絡中的最大生存時間。每種TCP協議的實現方法均要指定一個合適的MSL值,如RFC1122給出的建議值爲2分鐘,又如Berkeley體系的TCP實現通常選擇30秒作爲MSL值。這意味着TIME_WAIT的典型持續時間爲1-4分鐘。

TIME_WAIT狀態存在的原因主要有兩點:

1)爲實現TCP這種全雙工(full-duplex)連接的可靠釋放

參考本文前面給出的TCP釋放連接4次揮手示意圖,假設發起active close的一方(圖中爲client)發送的ACK(4次交互的最後一個包)在網絡中丟失,那麼由於TCP的重傳機制,執行passiveclose的一方(圖中爲server)需要重發其FIN,在該FIN到達client(client是active close發起方)之前,client必須維護這條連接的狀態(儘管它已調用過close),具體而言,就是這條TCP連接對應的(local_ip, local_port)資源不能被立即釋放或重新分配。直到romete peer重發的FIN達到,client也重發ACK後,該TCP連接才能恢復初始的CLOSED狀態。如果activeclose方不進入TIME_WAIT以維護其連接狀態,則當passive close方重發的FIN達到時,active close方的TCP傳輸層會以RST包響應對方,這會被對方認爲有錯誤發生(而事實上,這是正常的關閉連接過程,並非異常)。

2)爲使舊的數據包在網絡因過期而消失

爲說明這個問題,我們先假設TCP協議中不存在TIME_WAIT狀態的限制,再假設當前有一條TCP連接:(local_ip, local_port, remote_ip,remote_port),因某些原因,我們先關閉,接着很快以相同的四元組建立一條新連接。本文前面介紹過,TCP連接由四元組唯一標識,因此,在我們假設的情況中,TCP協議棧是無法區分前後兩條TCP連接的不同的,在它看來,這根本就是同一條連接,中間先釋放再建立的過程對其來說是“感知”不到的。這樣就可能發生這樣的情況:前一條TCP連接由local peer發送的數據到達remote peer後,會被該remot peer的TCP傳輸層當做當前TCP連接的正常數據接收並向上傳遞至應用層(而事實上,在我們假設的場景下,這些舊數據到達remote peer前,舊連接已斷開且一條由相同四元組構成的新TCP連接已建立,因此,這些舊數據是不應該被向上傳遞至應用層的),從而引起數據錯亂進而導致各種無法預知的詭異現象。作爲一種可靠的傳輸協議,TCP必須在協議層面考慮並避免這種情況的發生,這正是TIME_WAIT狀態存在的第2個原因。

具體而言,local peer主動調用close後,此時的TCP連接進入TIME_WAIT狀態,處於該狀態下的TCP連接不能立即以同樣的四元組建立新連接,即發起active close的那方佔用的local port在TIME_WAIT期間不能再被重新分配。由於TIME_WAIT狀態持續時間爲2MSL,這樣保證了舊TCP連接雙工鏈路中的舊數據包均因過期(超過MSL)而消失,此後,就可以用相同的四元組建立一條新連接而不會發生前後兩次連接數據錯亂的情況。

  1. socket api: close() 和 shutdown()

由前面內容可知,對一條TCP連接而言,首先調用close()的一方會進入TIME_WAIT狀態,除此之外,關於close()還有一些細節需要說明。

對一個tcp socket調用close()的默認動作是將該socket標記爲已關閉並立即返回到調用該api進程中。此時,從應用層來看,該socket fd不能再被進程使用,即不能再作爲read或write的參數。而從傳輸層來看,TCP會嘗試將目前send buffer中積壓的數據發到鏈路上,然後纔會發起TCP的4次揮手以徹底關閉TCP連接。

調用close()是關閉TCP連接的正常方式,但這種方式存在兩個限制,而這正是引入shutdown()的原因:

1)close()其實只是將socket fd的引用計數減1,只有當該socket fd的引用計數減至0時,TCP傳輸層纔會發起4次握手從而真正關閉連接。而shutdown則可以直接發起關閉連接所需的4次握手,而不用受到引用計數的限制;

2)close()會終止TCP的雙工鏈路。由於TCP連接的全雙工特性,可能會存在這樣的應用場景:local peer不會再向remote peer發送數據,而remote peer可能還有數據需要發送過來,在這種情況下,如果local peer想要通知remote peer自己不會再發送數據但還會繼續收數據這個事實,用close()是不行的,而shutdown()可以完成這個任務。

close()和shutdown()的具體調用方法可以man查看,此處不再贅述。

以上就是本文要分析和解決的“由於TIME_WAIT太多導致無法對外建立新連接”問題所需要掌握的基礎知識

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