2021-05-12 14:32:11
人人都應該懂點 TCP
即使你的工作也許不需要對TCP瞭如指掌,也不需要去了解具體的TCP/IP範例。你也應該懂一些基本的TCP知識,本文會告訴你為什麼。
我以前在Recurse Center工作的時候,曾經用Python寫過一個TCP棧(還寫了一篇博文 用Python實現TCP棧可以學到什麼)。這是很有意思的一課,也基本上是我對TCP的所有了解了。
一年之後,工作上遇到了困難。有同事在Slack上問到:“嘿,我向NSQ推訊息總是會有40ms的延遲,不知道為什麼。”這個問題我思來想去,過了一個週,還是毫無頭緒。
這裡解釋一下: NSQ是一個用來發訊息的佇列。傳送方式是向localhost發出一個HTTP請求,這個動作不可能花費40ms,一定是出了錯。但是NSQ不具備很高的CPU優先順序,也沒有占用大量記憶體,所以問題不是出在垃圾回收那邊。
後來,我想起來一週之前讀過的一篇文章——我們是如何在每一個POST請求上省出200ms的。這篇文章討論了一開始每一個POST都會多花200ms的原因,多少有些詭異。下面是這篇文章中的內容。
ACK延遲和TCP_NODELAY
Ruby的 Bet::HTTP 將POST請求分成兩個TCP包——一個header,一個body.curl,相比之下,將它們組合成一個倒是更加合適。不過更糟的是,Net:HTTP沒有給它開啟的TCP socket設定TCP_NODELAY,所以傳送第一個包之後,要等到確認才會傳送第二個。歸根結底,這是Nagle演算法導致的。
連線的另一端,HAProxy要選擇用何種方式確認這兩個包。在1.4.18(正式我們使用的版本),它使用的是TCP延時確認,延時確認在Nagle演算法中表現很糟糕,導致請求在這個地方暫停了,直至超時。
我來總結一下這段話:
- TCP是將你要傳送的資料打包的演算法
- 他們的HTTP需要用兩個小包傳送POST請求
整個過程就像下面這樣:
- application:嗨!給你第一個包
- HAProxy:噓……我們要等第二個包
- HAProxy:對了,我們要給他個確認,不過沒什麼大不了的,等會再說
- application:噓……我們等到第一個包的確認再發第二個,也許網路堵車了,再等一會
- HAProxy:煩死了,我們發第一個包的確認吧
- application:收到確認,發第二個包!!!!
- HAProxy:搞定!
這段時間內,HAProxy和application都在消極地等待,直到超過200ms。application等待是因為Nagle演算法,HAProxy等待是因為延遲ACK。
據我所知,延遲的ACK在所有Linux系統都是預設開啟的。所以這不是特例,只要你傳送的資料多於一個TCP包,你也會碰上這種事。
終於搞定了我們的問題
讀了這篇文章之後,覺得沒什麼了不起的。但是在我們的神秘40ms掙扎了許久,我想起來這篇文章。
我想:這可能是我的問題嗎?可能嗎??可能嗎?!我給團隊發了一封郵件說“可能是我瘋了,不過,有可能是TCP的問題。”
於是我將TCP_NODELAY
開啟,然後——BOOM!
所有的40ms延遲統統消失了,這個世界完美了。我真TM是個天才!
ACK延遲應該完全關閉嗎
提一個小插曲,我在HN上看到了這條評論:
真正的問題處在ACK延遲上。200ms延時設定是糟糕的主意,1985年在伯克利搞BSD的那幫人,根本不理解這個問題。ACK延遲是賭應用層一定會在200ms之內收到回復。雖然幾乎每次都輸,但是ACK延遲依然在用。
他在評論中討論了ACK是成本很低的,這中做法所導致的問題比它解決的問題要嚴重的多。
如果你不懂TCP,就搞不定這個問題
以前我總認為TCP是相當底層的東西,我永遠不需要去了解它。雖然差不多是這樣,但是實際生活中,你依然可能遇見和TCP演算法相關的bug,這時候懂一些TCP的知識就至關重要了。(本文也可以引申為,系統呼叫,作業系統這些都很重要,這個道理適用於很多東西)
ACK延時/TCP_NODELAY很糟糕——它可能對任何寫HTTP 請求程式碼的人造成影響。但是你不必成為系統程式設計方面的天才,懂一點TCP就幫我搞定了這個問題,也讓我意識到,出現這個問題我也有責任。我也在用strace,strace萬歲!
本文永久更新連結地址:http://www.linuxidc.com/Linux/2015-11/125625.htm
相關文章