為什么你必須得學些 TCP 的知識?
這不是指要明白 TCP 的所有東西,也不是說要通讀 《TCP/IP 詳解》。不過懂一點 TCP 知識是很有必要的。理由如下:
當我還在 Recurse Center 的時候,我用 Python 寫過 TCP 協議棧(還寫過一篇文章:如果你用 Python 寫 TCP 協議棧會遇到什么?)。這是一次有趣的學習經歷,但是也僅此而已。
一年以后,工作中有人在 Slack 上提到:“嘿,我在向 NSQ 發布消息時,每次要耗費 40 毫秒”。我已經斷斷續續思考了一個星期,但是沒有任何結果。
一點背景知識:NSQ 是一個消息隊列,你通過本地的一個 HTTP 請求向其發布消息。發送本地的一個 HTTP 請求確實不應該花費 40 毫秒,有時候會更差。NSQ 守護進程的負載不高,也沒有使用過多的內存,也看不到 GC 停頓。這究竟是為什么呢?神吶,救救我吧!
突然我記起我一周以前看過的一篇叫做“性能研究(In search of performance)”的文章——我們如何為每個 POST 請求節省 200ms。在這篇文章中,他們說到為什么每個 POST 請求會花費額外的 200 毫秒。就是這個原因。這是該文章中的關鍵段落:
延遲確認(ACK) 與 TCP_NODELAY
Ruby 的 Net::HTTP 會將 POST 請求切分為兩個 TCP 包,一個消息頭,一個消息體。相反,curl 會將這兩者合并為一個包。更糟糕的是,Net::HTTP 在打開 TCP 套接字時不會設置 TCP_NODELAY,這將導致第二個包需要等到第一個包的接收確認通知之后才能發送。這是 Nagle 算法導致的。
轉換到連接的另一端,HAProxy 需要決定如何確認這兩個包。在 1.4.18 版本中(我們正在用的版本),它是通過 TCP 延遲確認通知來實現的。延遲確認對 Nagle 算法有非常糟糕的影響,會導致請求暫停直到服務器延遲確認超時。
現在我們解釋這個段落說的內容。
TCP 是一個通過數據包傳輸數據的算法
他們的 HTTP 庫將 POST 請求分割成兩個小的數據包發送
接下來,TCP 采用類似如下的步驟進行交互:
application:Hi!這里有一個數據包。
HAProxy:(沉默),等待第二個包發送
HAProxy:對了,我需要返回一個確認,不過沒關系,等會吧
application: (沉默)
application:好吧,我正在等待確認,可能現在網絡延遲比較大
HAProxy:好吧,太煩人了,這是一個確認。
application:好極了,這是第二個數據包!!!
HAProxy:親,我們已經搞定了。
這個過程是不是應用程序和 HAProxy 都在消極等待另一方發送信息?這就是那額外的 200ms。應用程序這么做的是因為 Nagle 算法,而 HAProxy 消息等待的原因是延遲確認。
據我所知,延遲確認是所有 Linux 系統的默認行為。所以這不是一個偶然或者異常情況,如果發送 TCP 數據包多一個 1 個,你就會遇到這種情況。
現在,我們成為專家了
讀過這篇文章之后我很快就忘了。不過當我被額外的 40 毫秒難住的時候,我又記起來了。
所以我認為——這不可能是我的問題,可能嗎?可能嗎??然后我發了一封郵件給我團隊說:“我想我快要瘋了,但是這可能是 TCP 的問題”。
所以我提交了一次修訂,將我的應該調整為 TCP_NODELAY,然后問題就“嘣”的一聲解決了。
40 毫秒的延遲立馬就消失了。所有的事情都解決了,我就是個天才。
我們是否應該完全停止使用延遲確認?
我剛好在 Hacker News 看到 John Nagle (Nagle 算法的創始人)對 @alicemazzy 提到這個問題的評論。
本質問題是延遲確認。200 毫秒的“延遲確認”是一個非常不好的主意,1985 年中,在伯利克(Berkeley)研究 BSD 的人實際上沒有真正明白這個問題。延遲確認是應用層對 200 毫秒內是否響應的一場賭博,但是即便每次它都賭輸了,TCP 仍在使用延遲確認。
他繼續說到,確認本身是很小并且消耗很低的,延遲確認引起的問題可能比它解決的問題還要多。
不懂得 TCP 你就無法解決 TCP 問題
我曾經也認為,TCP 是一個相當底層的問題,我不需要明白。大多數時候你的確不需要明白。但是有的時候,當你在實踐中遇到由于 TCP 算法引起的 bug 時,懂點 TCP 知識就變得非常重要了。(正如我們經常在博客中討論的,許多事情都是這樣,比如系統調用和操作系統:) )
延遲確認及 TCP_NODELAY 的交互非常不好——這對任何語言實現的 HTTP 請求都有影響。你不需要很深入的去了解,成為系統程序專家。但是了解一點 TCP 是如何運作的,對我的工作的確大有裨益。通過對 TCP 的學習,我才意識到這篇博客所描述的問題也許正好是我所熟悉的領域。我也一直在使用 strace,并且會一直使用下去。