TCP短连接产生大量TIME_WAIT导致无法对外建立新TCP连接的原因及解决方法—基础知识篇...
? ?最近遇到一個線上報警:服務器出現(xiàn)大量TIME_WAIT導致其無法與下游模塊建立新HTTP連接,在解決過程中,通過查閱經(jīng)典教材和技術文章,加深了對TCP網(wǎng)絡問題的理解。作為筆記,記錄于此。
? ? ? ? 備注:本文主要介紹TCP編程中涉及到的眾多基礎知識,關于實際工程中對由TIME_WAIT引發(fā)的不能建立新連接問題的解決方法將在下篇筆記中給出。
1. 實際問題
? ? ? ? 初步查看發(fā)現(xiàn),無法對外新建TCP連接時,線上服務器存在大量處于TIME_WAIT狀態(tài)的TCP連接(最多的一次為單機10w+,其中引起報警的那個模塊產生的TIME_WAIT約2w),導致其無法跟下游模塊建立新TCP連接。
? ? ? ? TIME_WAIT涉及到TCP釋放連接過程中的狀態(tài)遷移,也涉及到具體的socket api對TCP狀態(tài)的影響,下面開始逐步介紹這些概念。
2. TCP狀態(tài)遷移
? ? ? ?面向連接的TCP協(xié)議要求每次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發(fā)送SYN并約定初始包序號(sequence number)為J;
? ? ? ? b. server發(fā)送自己的SYN并表明初始包序號為K,同時,針對client的SYNJ返回ACKJ+1(注:J+1表示server期望的來自該client的下一個包序為J+1);
? ? ? ? c. client收到來自server的SYN+ACK后,發(fā)送ACKK+1,至此,TCP建立成功。
? ? ? ? 其實,在TCP建立時的3次握手過程中,還要通過SYN包商定各自的MSS,timestamp等參數(shù),這涉及到協(xié)議的細節(jié),本文旨在拋磚引玉,不再展開。
? ? ? ? ? ?2)TCPConnection Termination
? ? ? ?與建立連接的3次握手相對應,釋放一條TCP連接時,需要經(jīng)過四步交互(又稱“四次揮手”),如下圖所示:
? ? ? ??
? ? ? ? ?可對上圖做如下解釋:
? ? ? ?a. 連接的某一方先調用close()發(fā)起主動關閉(active close),該api會促使TCP傳輸層向remotepeer發(fā)送FIN包,該包表明發(fā)起active close的application不再發(fā)送數(shù)據(jù)(特別注意:這里“不再發(fā)送數(shù)據(jù)”的承諾是從應用層角度來看的,在TCP傳輸層,還是要將該application對應的內核tcp send buffer中當前尚未發(fā)出的數(shù)據(jù)發(fā)到鏈路上)。 ? ? ? ? ? ? ? ?
? ? ? ?remote peer收到FIN后,需要完成被動關閉(passive close),具體分為兩步:
? ? ? ?b. 首先,在TCP傳輸層,先針對對方的FIN包發(fā)出ACK包(主要ACK的包序是在對方FIN包序基礎上加1);
? ? ? ?c. 接著,應用層的application收到對方的EOF(end-of-file,對方的FIN包作為EOF傳給應用層的application)后,得知這條連接不會再有來自對方的數(shù)據(jù),于是也調用close()關閉連接,該close會促使TCP傳輸層發(fā)送FIN。
? ? ? ?d. 發(fā)起主動關閉的peer收到remote peer的FIN后,發(fā)送ACK包,至此,TCP連接關閉。
? ? ? ?注意1:TCP連接的任一方均可以首先調用close()以發(fā)起主動關閉,上圖以client主動發(fā)起關閉做說明,而不是說只能client發(fā)起主動關閉。
? ? ? ?注意2:上面給出的TCP建立/釋放連接的過程描述中,未考慮由于各種原因引起的重傳、擁塞控制等協(xié)議細節(jié),感興趣的同學可以查看各種TCP RFC Documents ,比如TCP RFC793。
? ? ? ??3)TCP StateTransition Diagram
? ? ? ?上面介紹了TCP建立、釋放連接的過程,此處對TCP狀態(tài)機的遷移過程做總體說明。將TCP RFC793中描述的TCP狀態(tài)機遷移圖摘出如下(下圖引用自這里):
? ? ?
? ? ? ? ? TCP狀態(tài)機共含11個狀態(tài),狀態(tài)間在各種socket apis的驅動下進行遷移,雖然此圖看起來錯綜復雜,但對于有一定TCP網(wǎng)絡編程經(jīng)驗的同學來說,理解起來還是比較容易的。限于篇幅,本文不準備展開詳述,想了解具體遷移過程的新手同學,建議閱讀《Linux Network Programming Volume1》第2.6節(jié)。
3. TIME_WAIT狀態(tài)
? ? ? ??經(jīng)過前面的鋪墊,終于要講到與本文主題相關的內容了。 ^_^
? ? ? ? 從TCP狀態(tài)遷移圖可知,只有首先調用close()發(fā)起主動關閉的一方才會進入TIME_WAIT狀態(tài),而且是必須進入(圖中左下角所示的3條狀態(tài)遷移線最終均要進入該狀態(tài)才能回到初始的CLOSED狀態(tài))。
? ? ? ? 從圖中還可看到,進入TIME_WAIT狀態(tài)的TCP連接需要經(jīng)過2MSL才能回到初始狀態(tài),其中,MSL是指Max
Segment Lifetime,即數(shù)據(jù)包在網(wǎng)絡中的最大生存時間。每種TCP協(xié)議的實現(xiàn)方法均要指定一個合適的MSL值,如RFC1122給出的建議值為2分鐘,又如Berkeley體系的TCP實現(xiàn)通常選擇30秒作為MSL值。這意味著TIME_WAIT的典型持續(xù)時間為1-4分鐘。
? ? ? ?TIME_WAIT狀態(tài)存在的原因主要有兩點:
? ?? ? 1)為實現(xiàn)TCP這種全雙工(full-duplex)連接的可靠釋放
? ? ? ?參考本文前面給出的TCP釋放連接4次揮手示意圖,假設發(fā)起active close的一方(圖中為client)發(fā)送的ACK(4次交互的最后一個包)在網(wǎng)絡中丟失,那么由于TCP的重傳機制,執(zhí)行passiveclose的一方(圖中為server)需要重發(fā)其FIN,在該FIN到達client(client是active close發(fā)起方)之前,client必須維護這條連接的狀態(tài)(盡管它已調用過close),具體而言,就是這條TCP連接對應的(local_ip, local_port)資源不能被立即釋放或重新分配。直到romete peer重發(fā)的FIN達到,client也重發(fā)ACK后,該TCP連接才能恢復初始的CLOSED狀態(tài)。如果activeclose方不進入TIME_WAIT以維護其連接狀態(tài),則當passive close方重發(fā)的FIN達到時,active close方的TCP傳輸層會以RST包響應對方,這會被對方認為有錯誤發(fā)生(而事實上,這是正常的關閉連接過程,并非異常)。
? ? ? ? 2)為使舊的數(shù)據(jù)包在網(wǎng)絡因過期而消失
? ? ? ?為說明這個問題,我們先假設TCP協(xié)議中不存在TIME_WAIT狀態(tài)的限制,再假設當前有一條TCP連接:(local_ip, local_port, remote_ip,remote_port),因某些原因,我們先關閉,接著很快以相同的四元組建立一條新連接。本文前面介紹過,TCP連接由四元組唯一標識,因此,在我們假設的情況中,TCP協(xié)議棧是無法區(qū)分前后兩條TCP連接的不同的,在它看來,這根本就是同一條連接,中間先釋放再建立的過程對其來說是“感知”不到的。這樣就可能發(fā)生這樣的情況:前一條TCP連接由local peer發(fā)送的數(shù)據(jù)到達remote peer后,會被該remot peer的TCP傳輸層當做當前TCP連接的正常數(shù)據(jù)接收并向上傳遞至應用層(而事實上,在我們假設的場景下,這些舊數(shù)據(jù)到達remote peer前,舊連接已斷開且一條由相同四元組構成的新TCP連接已建立,因此,這些舊數(shù)據(jù)是不應該被向上傳遞至應用層的),從而引起數(shù)據(jù)錯亂進而導致各種無法預知的詭異現(xiàn)象。作為一種可靠的傳輸協(xié)議,TCP必須在協(xié)議層面考慮并避免這種情況的發(fā)生,這正是TIME_WAIT狀態(tài)存在的第2個原因。
? ? ? ?具體而言,local peer主動調用close后,此時的TCP連接進入TIME_WAIT狀態(tài),處于該狀態(tài)下的TCP連接不能立即以同樣的四元組建立新連接,即發(fā)起active close的那方占用的local port在TIME_WAIT期間不能再被重新分配。由于TIME_WAIT狀態(tài)持續(xù)時間為2MSL,這樣保證了舊TCP連接雙工鏈路中的舊數(shù)據(jù)包均因過期(超過MSL)而消失,此后,就可以用相同的四元組建立一條新連接而不會發(fā)生前后兩次連接數(shù)據(jù)錯亂的情況。
?4. socket api: close() 和 shutdown()
? ? ? ?由前面內容可知,對一條TCP連接而言,首先調用close()的一方會進入TIME_WAIT狀態(tài),除此之外,關于close()還有一些細節(jié)需要說明。
? ? ? ?對一個tcp socket調用close()的默認動作是將該socket標記為已關閉并立即返回到調用該api進程中。此時,從應用層來看,該socket fd不能再被進程使用,即不能再作為read或write的參數(shù)。而從傳輸層來看,TCP會嘗試將目前send buffer中積壓的數(shù)據(jù)發(fā)到鏈路上,然后才會發(fā)起TCP的4次揮手以徹底關閉TCP連接。
? ? ? ?調用close()是關閉TCP連接的正常方式,但這種方式存在兩個限制,而這正是引入shutdown()的原因:
? ? ? ?1)close()其實只是將socket fd的引用計數(shù)減1,只有當該socket fd的引用計數(shù)減至0時,TCP傳輸層才會發(fā)起4次握手從而真正關閉連接。而shutdown則可以直接發(fā)起關閉連接所需的4次握手,而不用受到引用計數(shù)的限制;
? ? ? ?2)close()會終止TCP的雙工鏈路。由于TCP連接的全雙工特性,可能會存在這樣的應用場景:local peer不會再向remote peer發(fā)送數(shù)據(jù),而remote peer可能還有數(shù)據(jù)需要發(fā)送過來,在這種情況下,如果local peer想要通知remote peer自己不會再發(fā)送數(shù)據(jù)但還會繼續(xù)收數(shù)據(jù)這個事實,用close()是不行的,而shutdown()可以完成這個任務。
? ? ? ?close()和shutdown()的具體調用方法可以man查看,此處不再贅述。
? ? ? ?以上就是本文要分析和解決的“由于TIME_WAIT太多導致無法對外建立新連接”問題所需要掌握的基礎知識。下一篇筆記會在本文基礎上介紹這個問題具體的解決方法。^_^
【參考資料】
1.《Linux Network Programming Volume 1》. Chapter 2 && Chapter 4
2. TCP RFC 793
3. Online Document: TCP StateTransition Diagram
================ EOF ===============
轉載于:https://blog.51cto.com/kusorz/1881309
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結
以上是生活随笔為你收集整理的TCP短连接产生大量TIME_WAIT导致无法对外建立新TCP连接的原因及解决方法—基础知识篇...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java毕业设计水库洪水预报调度系统源码
- 下一篇: Proteus8.6SP2仿真使用汇总