在最近的工作中需要對同一個域名下的源站同時發起多次請求,有時甚至達到了6000次,發生了很嚴重的性能問題,追查了下原因是被瀏覽器(Chrome)stalled了,因為瀏覽器只支持對同一個域名下保持6個連接,擁有更多連接時,就只能被掛起,直到上一個連接完成被復用。所以同時發起6000次請求,100ms的耗時也將會導致100秒的處理時間,在實際過程中復用連接將會導致更高的耗時。最終的解決方案當然是要求對方接口人改批量接口啦,皆大歡喜。
在追查的過程中也發現了前人關於請求被掛起導致加載緩慢的分析,最終追查到chrome日志,其中關於網絡連接引用到了TCP Reset的文章,對自己有一些啟發,這里翻譯並總結下。
在網絡服務中,經常性會遇到reset導致的連接斷開問題,每一次追查問題可能都會讓人大為惱火,但是首先我們需要了解reset是怎么工作的。reset事實上是一件好事情,它關閉那些已經沒有必要繼續存在的連接,舉個例子,我們的程序建立了許多的TCP短連接,這些連接在斷開時將會保持一段時間的Time Wait State狀態,默認4分鍾,用於保證連接能夠被正確的斷開,但是當我並不想這么做,想降低資源的消耗快速重用這些端口和資源時,就需要使用reset來重置這些連接了。從這里我們也能看出,重置實質上就是強制斷開連接。
三次握手
首先來說一下具體的TCP連接,老生常談的三次握手,它適用於基於IP協議的穩定網絡傳輸,保證信息是准確無誤的。當一個節點A想要跟另外一個網絡節點B通信時,A節點會先發送一個同步包Syn包,這個包將會包含建聯需要的基本信息,如源IP和端口以及目的IP和端口,以及序列值等信息。
如上面的包可以看到TCP:Flags=......S字段,這標識着這是一個Syn包,里面同時包含着Source IP, SrcPort, Destination IP, DstPort信息用於建聯,還有內容長度以及序列值等信息。這里的445端口是常用的SMB直連端口,為了本次能夠正確建聯服務端應該監聽這個端口並捕獲這個Syn包。
第二個包是Ack, Syn包,代表着服務端接收到了第一個Syn包並發送了自己的Syn包,這兩個動作發生在同一個包里,此時的包里可以看到源站IP和端口已經與目的IP和端口的位置互換了。最后一個包是客戶端確認接收到了服務端的Syn包並完成了本次建聯。
上面就是大家常說的三次握手的實際傳輸文本,現在兩個節點已經建聯並能夠進行穩定的數據傳輸了。這個穩定是對應於只基於IP協議的傳輸無法確認收到的是否是正確的數據。
Time Wait State
之前提到了Time Wait State,它的存在有什么意義呢?當三次握手建立TCP連接后,我們還需要四次分手來斷開連接,其中的包與上面類似,過程為:
1、客戶端A發送完了所有數據,向服務端B發送FIN包來請求斷開,A進入FIN_WAIT_1狀態。
2、服務端B發送Ack包給客戶端A,表示收到了斷開請求,A進入FIN_WAIT_2狀態。
3、當服務端B也發送完了自己的所有數據,向客戶端A發送FIN包請求斷開,B進入LAST_ACK狀態。
4、客戶端A收到了服務端B的請求,返回Ack包給B,然后進入TIME_WAIT狀態。
5、服務端B收到后,就斷開連接,不再確認。當經過2MSL(Maximum Segment Lifetime) 最大分段壽命后A沒有收到B的任何重傳信息,就代表連接被正常斷開了,A此時也斷開連接。
其中的MSL最大段壽命是一個TCP分段可以存在於互聯網系統中的最大時間,它通常被定義為兩分鍾長。保持2MSL的Time Wait State保證了這個連接的雜散數據能夠被正確傳遞到並且連接相關的資源被正確釋放。比如步驟4中A的Ack包並沒有被接收到,就會被B的重傳機制要求重傳,重傳成功后才可以正確斷開連接。
Reset
Reset是立即斷開TCP連接,釋放連接占用的資源並使得系統可以再次使用這些資源,如客戶端的端口號和服務端的fd(文件描述符)。
下面講幾個常見的Reset的場景,用於了解具體的操作。
SMB Reset
從Windows 2000開始,操作系統更希望監聽445端口而不是139端口來提供SMB服務,為了向下兼容,用戶需要同時發起兩個端口的Syn包,如果服務端也同時監聽了兩個端口,那么將會針對這兩個請求返回兩個Ack + Syn包。此時客戶端拿到兩個響應,將會給更偏愛的端口返回最后一次Syn包,同時對另一個端口進行Reset操作,如下圖所示:
Ack Reset
對於一個Syn包響應Ack Reset包,是在服務端接收到了客戶端的建聯請求,但是無法在請求的端口進行建聯的場景操作,原因可能是:
1、請求建聯的服務端並沒有監聽這個端口。
2、一些原因導致服務端無法在這個端口建聯成功,如資源被耗盡,從而無法建立起新的連接。在一些設備上如果沒有監聽該端口,請求將會被默認丟棄,而不是返回Ack + Reset包,這是出於安全性的考慮,比如防火牆。
無響應導致的Reset
在三次握手建聯之后,當一個網絡傳輸包傳輸失敗(沒有接收到該包的ack包超時)時,會進行重傳,並嘗試等待一段時間ack包,當重傳五次依然失敗,就會reset該連接。這里的重傳次數可以設置,默認為5。reset的原因是我們認為此時在兩個網絡節點間或希望發送ack包的節點上發生了問題,這也意味着本次連接變得不再有效。這里需要注意幾點:
1、重傳指的是同一個包重傳5次。
2、重傳的包之前的包傳輸不受影響。
3、遲到的ack不會導致reset,遲到的包會在后續超時重傳時成功被確認。
對用於TCP建聯的Syn包重傳的次數限制可以通過TCP-MaxConnectRetransmission字段設置,默認為2。
應用程序Reset
許多應用程序也會Reset連接,這些很難發現,如果我們排查了網絡問題和TCP本身沒有重置該連接,以及上面幾種情況的話,我們基本可以斷定是應用程序重置了該連接。一個很常見的場景,我們會在應用上建立很多的短連接,這樣的應用由於保持了太多的Time Wait State會導致端口資源被耗盡,所以這些等待的連接會被應用程序主動重置。但此時開發者應該明白Time Wait State存在的意義。我們可以通過查看應用中關於socket的代碼或查看socket日志來確認是否是應用重置了連接。如果三次握手的連接正常關閉,應該是采用FIN包的四次握手。還有一些更為罕見的場景是端口被搶占,常出現在系統重啟時不同應用搶占同一個端口來進行監聽。
網絡管理員關注的Reset
來自於網絡的reset,聽起來並不是那么靠譜,但事實上確實會發生。在建聯的兩個網絡節點之間的網絡設備會主動重置該連接,這種問題很難追查,最好的方法是使用traceroot來查看節點之間的連接,並同時對接收方和發送方的數據同時進行捕獲。在這種場景下,我們會發現發起reset請求的源ip並不是建聯的兩個網絡節點的任何一個,發生這種場景的原因有很多,比如交換機重啟,網絡設備故障等。有時候我們甚至會發現中間的設備對雙方都發起了reset請求。
重用端口
如果一個端口已經處於Time Wait State,這時希望復用源端口和目的端口發送Syn包,根據RFC 1122協議,是被允許的,但需要注意該狀態的含義(用於上個連接最后的FIN包被正確接收),此時的包seq值需要大於上次連接的最后一個包,否則將會被reset。
總結
TCP重置是一個好的事情,如果沒有reset,我們將會遇到各種各樣的TCP網絡連接問題。需要注意的是導致reset的原因是多種多樣的,不僅僅是兩端的節點還有可能是應用程序,追查問題時最重要的是查看包的狀態以及重傳。
server端不會考慮client的reset。
例如HTTP server是不會考慮client的reset,它收到第一個reset就認為是client放棄鏈接了。
server端只是被動端,盡量減少狀態交互,不應該對reset發起主動的動作。
如果客戶端頻繁SYN-RESET,那它是在DOS攻擊,這是不正常的,FW要管這個。
一般只有client要考慮server的reset,
這種問題就是看你要是不是要“我死都要連”或者是“我收到reset就玻璃心不連了”。
也就是說,client要根據reset的上下文或者應用本身的工作狀態來判斷接下來怎么辦
資料來源:https://segmentfault.com/a/1190000011285291
https://www.zhihu.com/question/27089240/answer/37331005