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