天天看點

TCP 三次握手原理,你真的了解嗎?

最近,阿裡中間件小哥哥蟄劍碰到一個問題——client端連接配接伺服器總是抛異常。在反複定位分析、并查閱各種資料文章搞懂後,他發現沒有文章把這兩個隊列以及怎麼觀察他們的名額說清楚。 是以,蟄劍寫下這篇文章,希望借此能把這個問題說清楚。歡迎大家一起交流探讨。
場景:JAVA的client和server,使用socket通信。server使用NIO。 1. 間歇性得出現client向server建立連接配接三次握手已經完成,但server的selector沒有響應到這連接配接。 2. 出問題的時間點,會同時有很多連接配接出現這個問題。 3. selector沒有銷毀重建,一直用的都是一個。 4. 程式剛啟動的時候必會出現一些,之後會間歇性出現。

正常TCP建連接配接三次握手過程:

TCP 三次握手原理,你真的了解嗎?

第一步:client 發送 syn 到server 發起握手;

第二步:server 收到 syn後回複syn+ack給client;

第三步:client 收到syn+ack後,回複server一個ack表示收到了server的syn+ack(此時client的56911端口的連接配接已經是established)。

從問題的描述來看,有點像TCP建連接配接的時候全連接配接隊列(accept隊列,後面具體講)滿了,尤其是症狀2、4. 為了證明是這個原因,馬上通過 netstat -s | egrep "listen" 去看隊列的溢出統計資料:

反複看了幾次之後發現這個overflowed 一直在增加,那麼可以明确的是server上全連接配接隊列一定溢出了。

接着檢視溢出後,OS怎麼處理:

tcp_abort_on_overflow 為0表示如果三次握手第三步的時候全連接配接隊列滿了那麼server扔掉client 發過來的ack(在server端認為連接配接還沒建立起來)

為了證明用戶端應用代碼的異常跟全連接配接隊列滿有關系,我先把tcp_abort_on_overflow修改成 1,1表示第三步的時候如果全連接配接隊列滿了,server發送一個reset包給client,表示廢掉這個握手過程和這個連接配接(本來在server端這個連接配接就還沒建立起來)。

接着測試,這時在用戶端異常中可以看到很多connection reset by peer的錯誤,到此證明用戶端錯誤是這個原因導緻的(邏輯嚴謹、快速證明問題的關鍵點所在)。

于是開發同學翻看java 源代碼發現socket 預設的backlog(這個值控制全連接配接隊列的大小,後面再詳述)是50,于是改大重新跑,經過12個小時以上的壓測,這個錯誤一次都沒出現了,同時觀察到 overflowed 也不再增加了。

到此問題解決,簡單來說TCP三向交握後有個accept隊列,進到這個隊列才能從Listen變成accept,預設backlog 值是50,很容易就滿了。滿了之後握手第三步的時候server就忽略了client發過來的ack包(隔一段時間server重發握手第二步的syn+ack包給client),如果這個連接配接一直排不上隊就異常了。

但是不能隻是滿足問題的解決,而是要去複盤解決過程,中間涉及到了哪些知識點是我所缺失或者了解不到位的;這個問題除了上面的異常資訊表現出來之外,還有沒有更明确地指征來檢視和确認這個問題。

深入了解TCP握手過程中建連接配接的流程和隊列

TCP 三次握手原理,你真的了解嗎?

如上圖所示,這裡有兩個隊列:syns queue(半連接配接隊列);accept queue(全連接配接隊列)。

三次握手中,在第一步server收到client的syn後,把這個連接配接資訊放到半連接配接隊列中,同時回複syn+ack給client(第二步);

題外話,比如syn floods 攻擊就是針對半連接配接隊列的,攻擊方不停地建連接配接,但是建連接配接的時候隻做第一步,第二步中攻擊方收到server的syn+ack後故意扔掉什麼也不做,導緻server上這個隊列滿其他正常請求無法進來。

第三步的時候server收到client的ack,如果這時全連接配接隊列沒滿,那麼從半連接配接隊列拿出這個連接配接的資訊放入到全連接配接隊列中,否則按tcp_abort_on_overflow訓示的執行。

這時如果全連接配接隊列滿了并且tcp_abort_on_overflow是0的話,server過一段時間再次發送syn+ack給client(也就是重新走握手的第二步),如果client逾時等待比較短,client就很容易異常了。

在我們的os中retry 第二步的預設次數是2(centos預設是5次):

如果TCP連接配接隊列溢出,有哪些名額可以看呢?

上述解決過程有點繞,聽起來懵,那麼下次再出現類似問題有什麼更快更明确的手段來确認這個問題呢?(通過具體的、感性的東西來強化我們對知識點的了解和吸收。)

netstat -s

比如上面看到的 667399 times ,表示全連接配接隊列溢出的次數,隔幾秒鐘執行下,如果這個數字一直在增加的話肯定全連接配接隊列偶爾滿了。

ss 指令

上面看到的第二列Send-Q 值是50,表示第三列的listen端口上的全連接配接隊列最大為50,第一列Recv-Q為全連接配接隊列目前使用了多少。

全連接配接隊列的大小取決于:min(backlog, somaxconn) . backlog是在socket建立的時候傳入的,somaxconn是一個os級别的系統參數。

這個時候可以跟我們的代碼建立聯系了,比如Java建立ServerSocket的時候會讓你傳入backlog的值:

(來自JDK幫助文檔:https://docs.oracle.com/javase/7/docs/api/java/net/ServerSocket.html)

半連接配接隊列的大小取決于:max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog),不同版本的os會有些差異。

我們寫代碼的時候從來沒有想過這個backlog或者說大多時候就沒給他值(那麼預設就是50),直接忽視了他,首先這是一個知識點的盲點;其次也許哪天你在哪篇文章中看到了這個參數,當時有點印象,但是過一陣子就忘了,這是知識之間沒有建立連接配接,不是體系化的。但是如果你跟我一樣首先經曆了這個問題的痛苦,然後在壓力和痛苦的驅動自己去找為什麼,同時能夠把為什麼從代碼層推理了解到OS層,那麼這個知識點你才算是比較好地掌握了,也會成為你的知識體系在TCP或者性能方面成長自我生長的一個有力抓手。

netstat 指令

netstat跟ss指令一樣也能看到Send-Q、Recv-Q這些狀态資訊,不過如果這個連接配接不是Listen狀态的話,Recv-Q就是指收到的資料還在緩存中,還沒被程序讀取,這個值就是還沒被程序讀取的 bytes;而 Send 則是發送隊列中沒有被遠端主機确認的 bytes 數。

netstat -tn 看到的 Recv-Q 跟全連接配接半連接配接沒有關系,這裡特意拿出來說一下是因為容易跟 ss -lnt 的 Recv-Q 搞混淆,順便建立知識體系,鞏固相關知識點 。

比如如下netstat -t 看到的Recv-Q有大量資料堆積,那麼一般是CPU處理不過來導緻的:

TCP 三次握手原理,你真的了解嗎?

上面是通過一些具體的工具、名額來認識全連接配接隊列(工程效率的手段)。

實踐驗證一下上面的了解

把java中backlog改成10(越小越容易溢出),繼續跑壓力,這個時候client又開始報異常了,然後在server上通過 ss 指令觀察到:

按照前面的了解,這個時候我們能看到3306這個端口上的服務全連接配接隊列最大是10,但是現在有11個在隊列中和等待進隊列的,肯定有一個連接配接進不去隊列要overflow掉,同時也确實能看到overflow的值在不斷地增大。

Tomcat和Nginx中的Accept隊列參數

Tomcat預設短連接配接,backlog(Tomcat裡面的術語是Accept count)Ali-tomcat預設是200, Apache Tomcat預設100。

Nginx預設是511

因為Nginx是多程序模式,是以看到了多個8085,也就是多個程序都監聽同一個端口以盡量避免上下文切換來提升性能

全連接配接隊列、半連接配接隊列溢出這種問題很容易被忽視,但是又很關鍵,特别是對于一些短連接配接應用(比如Nginx、PHP,當然他們也是支援長連接配接的)更容易爆發。 一旦溢出,從cpu、線程狀态看起來都比較正常,但是壓力上不去,在client看來rt也比較高(rt=網絡+排隊+真正服務時間),但是從server日志記錄的真正服務時間來看rt又很短。

jdk、netty等一些架構預設backlog比較小,可能有些情況下導緻性能上不去。

希望通過本文能夠幫大家了解TCP連接配接過程中的半連接配接隊列和全連接配接隊列的概念、原理和作用,更關鍵的是有哪些名額可以明确看到這些問題(工程效率幫助強化對理論的了解)。

另外每個具體問題都是最好學習的機會,光看書了解肯定是不夠深刻的,請珍惜每個具體問題,碰到後能夠把來龍去脈弄清楚,每個問題都是你對具體知識點通關的好機會。

最後提出相關問題給大家思考

全連接配接隊列滿了會影響半連接配接隊列嗎?

netstat -s看到的overflowed和ignored的數值有什麼聯系嗎?

如果client走完了TCP握手的第三步,在client看來連接配接已經建立好了,但是server上的對應連接配接實際沒有準備好,這個時候如果client發資料給server,server會怎麼處理呢?(有同學說會reset,你覺得呢?)

來源:阿裡技術微信公衆号

♥ 作者:明志健緻遠

♠ 出處:http://www.cnblogs.com/study-everyday/

♦ 本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。

♣ 本部落格大多為學習筆記或讀書筆記,本文如對您有幫助,還請多推薦下此文,如有錯誤歡迎指正,互相學習,共同進步。