爲何你得學些 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 毫秒。就是這個緣由。這是該文章中的關鍵段落:app

延遲確認(ACK) 與 TCP_NODELAY

Ruby 的 Net::HTTP 會將 POST 請求切分爲兩個 TCP 包,一個消息頭,一個消息體。相反,curl 會將這二者合併爲一個包。更糟糕的是,Net::HTTP 在打開 TCP 套接字時不會設置 TCP_NODELAY,這將致使第二個包須要等到第一個包的接收確認通知以後才能發送。這是 Nagle 算法致使的。curl

 

轉換到鏈接的另外一端,HAProxy 須要決定如何確認這兩個包。在 1.4.18 版本中(咱們正在用的版本),它是經過 TCP 延遲確認通知來實現的。延遲確認對 Nagle 算法有很是糟糕的影響,會致使請求暫停直到服務器延遲確認超時。tcp

如今咱們解釋這個段落說的內容。函數

  • 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,而且會一直使用下去。

 

 

分享案例之一:TCP

斜體部分位讀者的提問

你好,請教你一個問題,在用tcp通信時,應用層每次發送包的大小是儘可能一個mtu之內呢仍是無所謂?在移動網絡下跟pc下分別如何?個人理解是tcp既然是流式的,而且本身會切包,超時重傳也是基於切要後的包(不像udp按照切完前),因此無所謂,不知道這樣理解是否是正確的?

 

TCP/IP協議模塊,在應用層看來,就是本身的本地代理,代理本身與別的計算機程序通訊。

代理算好聽的,說難聽一點就是一羣打雜的。TCP/IP將底層通訊有關的全部細節都大包獨攬了,留下有限幾個接口函數,讓應用層給本身發號施令(^_^)。

接口函數,常常被寫做API,API是Application Programming Interface的縮寫,若是沒有這些API接口,應用層如何對那些底層幹活的發號施令?

先假設應用層須要傳輸一個10M的文件,應用層經過send()接口函數發送數據,問題來了,send()最大能夠接受多少字節的數據?

這個是send()函數的實現限制,太大了超過TCP的緩衝區確定不行,最好每次發送的數據不超過TCP緩衝區的一個單元的大小。

因此應用層會遵守send()的大小限制,每次發送的數據都在最大限度之內,這個算分片嗎?

算第一次分片,可是這個是沒法避免的!可是這個過程耗費的資源比較少,只是順序讀取10M字節過程,每次讀取的字節長度有限制。

 

TCP接到send()發送的字節,會立馬保持原封不動發送嗎?

不肯定!

TCP會先將這些數據放在本身的倉庫(發送緩衝區),至於何時發,每次發多少,已經不是應用層所能左右的了。

TCP是基於字節流發送,可能將用戶的一次數據發送砍成多個segment發送,也可能將屢次應用層的發送合併在一個segment發送。而決定TCP一次最大能發segment的大小則和MSS有關,而MSS最終和MTU有關。

終於說到了MTU,你們可能知道MTU是數據鏈路層對網絡層的限制,以最多見的Ethernet鏈路爲例,Ethernet MTU = 1500,這個意思是Ethernet Payload最大的尺寸不得超過1500字節。這1500字節包含Ethernet頭部、尾部嗎?

固然不包含!

那問題又來了,Ethernet幀若是加上4個字節的802.1Q的頭部,MTU =
1500 裏包含4字節的802.1Q嗎?

包含!

這樣操做的話,意味着用戶的IP報文若是是1500字節,添加一個802.1Q會變成1504,意味着須要分片處理!

分片是很是不利的選擇,當前網絡所作的不少努力都是極力避免分片!

如何避免分片呢?

其實很簡單,凡是可能添加802.1Q接口將MTU = 1504,甚至1508,這樣即便添加1個、2個802.1Q頭,用戶1500字節的IP報文也不會被分片。

Okay,再回到正題,你們有沒有思考過爲什麼Ethernet要有MTU的限制?

Ethernet最初對IP報文也沒有什麼限制,IP報文最大能夠有65535字節長,可是發現Ethernet對於長報文沒法可靠地傳輸,而將報文限制在必定的尺寸,Ethernet能夠將報文大機率傳輸到目的地,因而就有了MTU這一限制措施,MTU是爲了更可靠地傳輸數據。

既然物理硬件有了MTU的限制,那麼但願TCP/IP將這種硬性的限制措施,層層向上傳達,因而TCP選擇了MSS來限制發送segment的大小。

MSS = MTU – IP Header – TCP Header = 1500 -20
-20 = 1460

TCP MSS真的能夠避免分片嗎?

TCP鏈接的雙方依據本地物理鏈路的MTU,按照以上的公式計算出本地的MSS,而後雙方交換各自的MSS,雙方會選擇二者中小的MSS來繼續通訊。但有沒有想過,若是路徑中的MTU比鏈接雙方的MTU都小,是否是分片就沒法避免了?答案是確定的!

這個時候必須分片,不分片就會丟,這是DF = 1的情況,須要給源主機發送ICMP消息,問題是ICMP消息能到達源主機嗎?若是不能到達,通訊就會斷,即便TCP有重傳機制。

能到達源主機,源的TCP意識到這一點,會將重傳的報文從新切片,從新發送,這沒有什麼問題,只是耽誤一點時間而已。

DF = 0時,能夠直接分片,儘管耗費不少分片的資源,到達目的地再重組,也要耗費一點資源。

問題是,沒有端口號的分片,通過安全設備時,可能會遇到障礙,這一樣會形成通訊的障礙。

看,一旦分片形成多大的麻煩,麻煩意味着CPU資源的耗費,爲了不這些沒必要要的動做,只要不分片,一切都會變的簡單。

UDP

UDP是塊式消息,UDP自己沒有任何分片的能力,也沒有任何重傳的能力,這些能力須要依賴應用層、IP層。

計算機網絡發展到今天,你們已經造成了一個共識,若是用戶的數據確實須要分片傳輸,務必保證分片的動做由應用層來完成,到達目的地由應用層將字節流,再整理成有意義的消息塊。

相關文章
相關標籤/搜索