高性能瀏覽器網絡之網絡部分 -- Building Blocks of TCP

Building Blocks of TCP -- 題目怎麼翻譯呢? TCP構建塊? 深入淺出TCP?認識TCP?TCP的構建?

---------------------------------------------------------------------------------------

第二章 Building Blocks of TCP


互聯網的核心是兩個協議,IP和TCP。IP(Internet Protocol),提供主機到主機的路由和尋址, TCP(Transmission Control Protocol),提供運行在不可靠通道上的可靠網絡抽象。TCP/IP通常指Internet Protocol Suite,它由Vint Cerf 和Bob Kahn在他們的論文“A Protocol for Packet Network Intercommunication”中第一次提出。
最初的提議(RFC 675)被修正了很多次,第四版本的TCP/IP規格於1981年發佈,被分爲兩個獨立的RFCs:
  • RFC 791 -- Internet Protocol
  • RFC 793 -- Transmission Control Protocol
自那以後,出現了很多改善建議,並被應用與TCP,但核心操作一直沒有多大變化。TCP迅速取代了之前的協議,現在已被大部分流行應用所採用:World Wide Web,email, file transfers, and 很多其它應用。
TCP提供了有效的運行於不可靠通道上的可靠網絡抽象。對應用程序隱藏了大部分網絡通信的複雜性:重傳丟失數據、按序發送、擁塞控制和避免、數據完整性等等。當你使用TCP流時,TCP保證你發送什麼對方就收到什麼,而且保證數據按序到達。正因如此,TCP被優化用於準確發送,而不是及時快速發送。結果,當要在瀏覽器中優化網絡性能時,這給我們帶來了一些挑戰。

HTTP標準並沒有指定非得用TCP進行傳輸。如果你願意,你也可以用UDP或其它任何傳輸協議傳輸HTTP,但實際上,由於TCP提供了許多易用的,強大的功能,所以,今天的互聯網上所有的HTTP通信都是基於TCP的。

所以,理解一些TCP的核心機制是構建最優網絡體驗的必備知識。沒準在你的應用程序中你不會直接同TCP sockets打交道,但你在應用層的設計決策可能會影響到TCP和底層網絡的性能。

三次握手

所有的TCP連接開始於三次握手(圖2-1)。在客戶端與服務器能夠交換任何應用數據之前,它們必須就初始包系列號,以及雙方的若干連接參數達成協議。出於安全考慮,雙方的初始系列號都是隨機產生。

SYN
        客戶端挑選一個隨機系列號x併發送一個SYN報文,其中可能包含額外的TCP標誌和選項
SYN ACK
        服務器給x加1,挑選自己的隨機系列號y,添加它自己的標誌集和選項,然後發送響應
ACK
        客戶端遞增x和y,併發送握手過程的最後ACK報文以完成握手
       

一旦三次握手完成,應用數據就可以開始在客戶端與服務器之間流動了。客戶端可以在ACK報文之後立即發送一個數據包,服務器必須等到收到ACK之後才能發送數據。這個啓動過程適用於每一個TCP連接,而且對所有使用TCP的網絡應用的性能有重要影響:在能夠發送應用數據之前,每一個新連接都會有一個完整的往返延遲。

例如,假設客戶端位於紐約,服務器位於倫敦,我們通過光纖鏈路啓動一個新的TCP連接,那麼三次握手將花費56毫秒(表 1-1):單方向上傳播數據包需要28毫秒,之後它還必須返回到紐約。注意這裏不考慮連接的帶寬。延遲主要由兩個城市之間的距離決定。

由三次握手強加的延遲使得創建新的TCP連接變得很“昂貴”,這也是爲何重用連接對運行在TCP之上的任何應用程序都是一個關鍵優化項的很大原因。

TCP快速打開
TCP握手階段已經被確認爲總網頁瀏覽時延的一個重要來源,很多程度上是由於,對於很多網頁,它的幾十到上百個子資源分佈在不同的主機上,需要大量很短的TCP流來獲取這些資源。
TCP Fast Open是一個旨在減少新的TCP連接強加的延遲懲罰的機制。基於Google所做的流量分析和網絡模擬,研究人員已經證明,TFO,它允許SYN報文攜帶數據,能夠HTTP事務網絡延遲降低15%,整個網頁平均加載時間減少10%,在某些高延遲的場景甚至達到40%。

在linux 3.7+ 內核中,客戶端和服務器都可以獲得TFO支持。花雖如此,TFO並不能解決所有問題。它可以幫助消除三次握手的往返懲罰,但它只能用於特定場景:SYN報文能夠攜帶的數據有效載荷大小是有限制的,只能發送特定類型的HTTP請求,因爲它需要一個加密的cookie,所以只能用於重複的連接。

擁塞避免和控制

早在1984年,John Nagle記錄了一個被稱爲“擁塞奔潰”的情況(condition,這個怎麼翻合適呢??),它會對節點之間任何非對稱帶寬網絡造成影響:

擁塞控制是複雜網絡中一個公認的問題。我們發現,當這兩個協議,國防部的Internet Protocol(IP),一個純粹的數據包協議,和傳輸控制協議(TCP),一個傳輸層協議,一起使用時,由於傳輸層和數據報層的交互,會引起不尋常的擁塞問題。尤其是,IP網關很容易發生“擁塞奔潰”現象,特別是在它連接到一個帶寬差別很大的網絡時。
一旦往返時間超過了主機的最大傳輸間隔,主機就會開始發送越來越多的相同數據包的副本到網絡中。現在網絡就麻煩了。最終交換節點的可用緩衝將被填滿,後面的包只能丟掉。被髮送的數據包的往返時間現在達到了最大值。主機不斷的重發數據包,最終,每一個包都有多個副本到達目標主機。這就是擁塞奔潰。
這種情況是穩定的。一旦達到飽和點,如果挑選丟棄包的算法是公平的,網絡將繼續在退化狀態下運行。
                                                                                                                                             -- John Nagle RFC896

該報告認爲擁塞奔潰還沒有成爲ARPANET的問題,是因爲它的大部分節點帶寬相同,而且骨幹網有大量富餘帶寬。然而,不久之後,這些斷言就不正確了。1986年,隨着大量(5000+)、各種各樣的節點加入網絡,一系列的擁塞奔潰橫掃網絡 -- 在有的情況下,網絡帶寬甚至下降了1000倍,網絡變得沒法再使用。
爲了解決這一問題,在TCP中實現多種機制,以控制兩個方向上的數據發送速率:流控制,擁塞控制,擁塞避免。

ARPANET是現代互聯網的先驅,是世界上第一個包交換網絡。該項目正式啓動於1969年,1983年,TCP/IP取代了早期的NCP,成爲主要的通信協議。後來的事,便是總所周知的歷史了。

流控制

流控制是一種防止發送方發送過多數據導致接收方處理不過來的機制 -- 接收端可能在忙、負載嚴重、或者不願意再分配更多緩衝空間。爲了解決這個問題,TCP連接的每一端都通告(圖2-2)它自己的接收窗口(rwnd),它用於溝通用於接收數據的緩衝空間的大小。
當連接第一次建立時,每一端都使用它們的系統默認設置來初始化rwnd的值。瀏覽網頁時,大多數的數據流是從服務器到客戶端,所以客戶端的窗口是可能的瓶頸。然而,在上傳大量數據,比如圖片或視頻到服務器時,服務器接收窗口可能成爲限制因素。
任何一端由於某種原因跟不上了, 它都可以向發送端通告一個更小的接收窗口。如果接收窗口變爲0,就表示不應該再發送數據了。這一流程貫穿每一個TCP連接的整個生命週期:每一個ACK攜帶有每一端的最新的rwnd值,發送端可以據此動態調整發送速率,以協調發送者和接收者的速率。



窗口伸縮(Window Scaling RFC 1323)
最初的TCP規範爲通告接收窗口大小分配了16位,這就給能通告的接收窗口的最大值做了一個硬性限制。結果表明,爲了獲得最佳性能,這個硬上限設置往往是不夠的,尤其是在有高帶寬時延乘積的網絡中。
爲了解決這個問題,起草了RFC1323以提供“TCP Window Scaling”選項。它允許我們把接收窗口的最大值從65535字節提升到1G字節!窗口擴大選項是在三次握手時進行溝通的,它的值表示在後面的ACKs中,16位的窗口大小域應該左移的位數。
如今,基本上所有主流平臺都是默認支持窗口擴大選項的。然而,中間節點,路由器,防火牆可能重填甚至刪除該選項。如果你與服務器,或客戶端的連接,不能完全利用可用帶寬,那麼檢查一下你的窗口大小的互動總是一個良好的開端。在Linux平臺,能夠通過如下命令,檢查和設置窗口伸縮選項:
  • $> sysctl net.ipv4.tcp_window_scaling
  • $> sysctl -w net.ipv4.tcp_window_scaling=1

慢啓動

儘管在TCP中有了流控制,網絡擁塞奔潰仍然在19世界中後期成爲了一個真正的問題。問題是流控制防止了發送端淹沒接收端,但是沒有機制來防止這兩端淹沒底層網絡。發送端和接收端在一開始的時候都不知道網絡的可用帶寬,因此需要一種機制來估計可用帶寬並調整它們的速度以適應網絡不斷變化的情況。
爲了說明這種調整適應是有益的,想象你在家觀看一個來自遠程主機的大視頻,爲了提供最好的質量體驗,它佔滿了你的下行鏈路。然後,你家中的另一個用戶打開一個新的連接以下載一些軟件更新。突然,視頻流可用的下行帶寬變少了,視頻服務器必須調整它的數據發送速率 -- 否則,如果它繼續保持相同的速率的話,數據只會簡單的堆積在某些中間網關而且大量數據包將會被丟棄,最終導致低效的網絡使用。
1988年, Van Jacobson 和 Michael J.Karels記錄了幾個解決這些問題的算法:慢啓動,擁塞避免,快速重傳,以及快速恢復。這四種算法很快就成爲了TCP規範的強制部分。普遍認爲,正是這些TCP的更新使得Internet沒有在80年代和90年代處隨着網絡流量呈指數增長的情況下崩潰。
想要理解慢啓動,最好看看它的實際應用。那麼,讓我們再次回到我們的客戶端,它位於紐約,想要從倫敦的服務器上獲取一個文件。首先,執行三次握手,這期間雙方在ACKs包中通告它們各自的rwnd大小(圖 2-2)。一旦最終的ACK包發送到線路上,我們就可以開始交換應用數據了。
估計客戶端與服務器之間可用帶寬的唯一辦法是通過交換數據來測量,而這正是慢啓動被設計要做的。啓動時,服務器爲每個TCP連接初始化一個新的擁塞窗口(cwnd)變量,並初始化爲一個保守,由系統指定的值(在Linux上是initcwnd)。

擁塞窗口大小(cwnd)
        再收到客戶端的確認(ACK)前,發送端能發送到網絡的數據大小限制。

cwnd變量並不會在發送者和接收者之間通告或交換 -- 在這個例子中,它將是一個由倫敦的服務器維護的私有變量。此外,介紹一個新的規則:在客戶端與服務器之間的網絡上(沒有確認)的最大數據量是rwnd和cwnd變量的最小值。目前爲止,一切順利,但是問題來了,客戶端與服務器怎麼選擇一個最優的擁塞窗口值呢?畢竟,即便是在相同的兩個網絡節點之間,網絡狀況也總是在變,這我們在之前的例子中已經看到過了,如果我們能夠通過算法而不必手工調整每個連接的窗口大小那就太棒了。
解決方法是慢啓動並隨着包的確認而增加窗口大小:慢啓動!原來,cwnd的啓動值被設置爲1個網絡段;1999年4月,RFC 2581把這個值更新爲最大到4個報文段,最近,2013年4月,RFC 6928再一次被增加到了10個報文段。
一個新的TCP連接的最大飛行(in flight)數據量是rwnd 和 cwnd這兩個值的最小值;因此服務器能夠發送多達4個網絡段給客戶端,然後它必須停下來等待確認。隨後,每確認一個包,根據慢啓動算法,服務器可以使cwnd值增加1 -- 確認了一個包,可以發送兩個新的包。TCP連接的這個階段通常被稱爲“指數增長”算法(圖 2-3),因爲客戶端和服務器都試圖快速收斂於它們之間網絡路徑的可用帶寬。

       
慢啓動是我們在開發瀏覽器時需要牢記的一個重要因素,爲什麼呢?HTTP和許多其它應用協議運行在TCP之上,而且無論帶寬怎麼樣,每一個TCP連接都必須經歷慢啓動階段 -- 我們不能立即完全利用帶寬。
我們以一個小的擁塞窗口開始,並在每一個往返時間後使其加倍。這樣,達到指定目標吞吐量的時間就是RTT和初始擁塞窗口大小的函數:
Equation 2-1. Time to reach the cwnd size of size N
                                             
下面舉例說明慢啓動的影響:假定
  • 客戶端和服務端接收窗口大小65,535字節(64KB)
  • 初始擁塞窗口大小:4報文段 (RFC 2581)
  • RTT: 56ms (倫敦到紐約)
在這個例子以及後面的例子中,我們將使用舊的4個網絡段的值作爲擁塞窗口的初始值,因爲大部分服務器仍然使用這個值。後面的例子應該能夠很好的激勵你更新你的服務器!
儘管接收窗口的值是64KB,但新的TCP連接的吞吐量由擁塞窗口大小做了初始限制。事實上,爲了達到64KB限制,我們將需要把擁塞窗口增加到45個報文段,這將花費224毫秒。
                                             
爲了達到64KB的吞吐量,需要四個往返時間(圖 2-4),總計幾百毫秒的延遲。事實上客戶端與服務器可能完全能夠以Mbps+的速度進行傳輸 -- 這就是慢啓動!



爲了減少增長擁塞窗口所花費的時間,我們可以減少客戶端與服務器之間的RTT -- eg., 把服務器移到地理上靠近客戶端的地方。或者增加初始擁塞窗口值到新的RFC 6928規定的10個報文段。
慢啓動對於大數據量的流下載不是什麼大問題,因爲客戶端和服務器在幾百毫秒後將達到它們的最大窗口值,並在之後以接近最大值的速度進行傳輸 -- 慢啓動的成本在這種長時間、大數據的傳輸中就顯得微不足道。
然而,很多的HTTP連接,通常是突發性的,而且很短,在請求終止前達到最大窗口值的情況很少見。所以,很多web應用的性能通常受客戶端與服務器之間的RTT所限:慢啓動限制了可用的帶寬吞吐量,這對小傳輸的性能有不利影響。


慢啓動重啓
除了控制新連接的傳輸率,TCP還實現了慢啓動重啓(SSR)機制,一個連接空閒一段時間後,該機制就會重設擁塞窗口。基本原理很簡單:在網絡空閒期間,網絡狀況可能已經變化了,爲了避免擁塞,窗口被重設爲一個安全的默認值。
SSR對空閒了一段時間的長TCP連接的性能有重要影響,這也沒什麼奇怪的 -- eg., HTTP 長連接。所有,推薦在服務器端關閉SSR。在Linux平臺,可用通過如下命令檢查和關閉SSR:
  • $> sysctl net.ipv4.tcp_slow_start_after_idle
  • $> sysctl -w net.ipv4.tcp_slow_start_after_idle=0
爲了說明TCP三次握手和慢啓動階段對簡單HTTP傳輸的影響,讓我們假定我們位於紐約的客戶端要通過TCP連接請求位於倫敦的服務器上的一個20KB的文件(圖 2-5),連接參數如下:
  • RTT:56ms
  • 帶寬:5 Mbps
  • 接收窗口大小: 65,535字節
  • 初始擁塞窗口:4 segments(4x1460字節 = 5.7KB)
  • 服務器處理時間:40ms
  • 沒有包丟失,一包一確認,GET請求位於單個報文段中


0 ms       客戶端通過SYN包開始握手
28 ms     服務器響應SYN-ACK並指定它的rwnd大小
56 ms     客戶端確認SYN-ACK, 指定它的rwnd大小, 並立即發送了HTTP請求
84 ms     服務器收到HTTP請求
124 ms   服務器生成完20KB的響應併發送4個報文段,之後暫停等待ACK
152 ms   客戶端收到4個報文段並對每一個進行確認
180 ms   服務端收到ACK後增加它的cwnd,併發送8個報文段
208 ms   客戶端收到8個報文段,併發送確認
236 ms   服務端收到ACK後增加它的cwnd,併發送剩餘報文段
264 ms   客戶端收到剩餘報文段,併發送確認

作爲一個練習,把cwnd值設爲10個報文段替換原來的4,然後在跑一遍 圖 2-5。你應該會看大一個完整的RTT延遲消失了 -- 性能提升了22%!

花費了264ms來在56ms的往返時間的客戶端與服務器之間傳輸一個20KB的文件!作爲一個對比,現在,讓我們假設客戶端能夠重用同樣的TCP連接(圖 2-6)並再一次發送了同樣的請求。


0 ms      客戶端發送HTTP請求
28 ms    服務器收到HTTP請求
68 ms    服務器生成完20KB的響應,但是cwnd的值已經比發送文件需要的15個報文段大了;因此它一次性發送了所有的報文段
96 ms    客戶端收到了所有的15個報文段,然後發送確認
同樣的連接,同樣的請求,但是沒有三次握手的代價和慢啓動階段的懲罰,現在只需要96ms,也就是性能提升了275%!

在這兩個例子中,客戶端和服務器都能夠訪問上行5Mbps的帶寬,這個事實在TCP啓動階段沒什麼影響。三次握手延遲和擁塞窗口值纔是限制因素。
事實上,如果我們增加RTT,這兩個例子的性能差距也將擴大。作爲一個練習,你可以用一個不同的值來試試。一旦你對TCP擁塞控制有了直觀的認識,你可能就會自覺的使用諸多優化方法,比如:長連接,pipelining, 複用。

增加TCP初始擁塞窗口
在服務器端增加初始擁塞窗口的值爲新的RFC 6928規定的10個報文段,是最簡單的提高所有用戶和運行於TCP之上的應用程序性能的方法之一。好消息是,許多操作系統已經更新了它們最近的內核以使用這個更大的值 -- 檢查相應的文檔和release notes。
對於Linux,IW10是所有2.6.39之後內核的新的默認值。不過,不要就此滿足:升級到3.2+, 還可以獲得其它重要更新的益處;

擁塞避免

TCP是專門設計使用包丟失作爲幫助控制它的性能的反饋機制,認識到這一點是很重要的。換句話說,不應該說如果包丟失怎麼怎麼樣,而應該說包丟失的時候怎麼怎麼樣。慢啓動以一個保守的窗口值來初始化連接,並且每一次往返加倍飛行數據的總量,直到超過接收者流控制窗口、系統配置的擁塞閥值(ssthresh)窗口,或者發生包丟失,然後,擁塞避免算法開始接手(圖 2-3)。
在擁塞避免機制中隱含了一個假設,包丟失表示網絡發生了擁塞:沿途的某個地方,一條鏈路或一個路由器發生了擁塞,不得不丟棄包,因此我們不得不調整我們的窗口以避免引起更多的包丟失和淹沒網絡。
擁塞窗口被重設時,擁塞避免指定它自己的算法用於計算如何增長窗口大小,以最大限度減少進一步的包丟失。在某個點,可能發生另一個包丟失,這個過程將再一次重複。如果你曾經對TCP連接做過吞吐量追蹤,並且在其中發現鋸齒模式,那現在你就知道爲什麼會是這樣了:擁塞控制和避免算法根據包丟失情況調整擁塞窗口大小。
最後,值得注意的是,改進擁塞控制和避免在學術研究和商業產品上都是一個活躍的領域:爲了適應不同的網絡類型,不通過的數據傳輸類型等等。如今,依賴於運行的系統,你可能正在運行衆多變種中的一個:TCP Tahoe and Reno(最初的實現),TCP Vegas, TCP New Reno, TCP BIC, TCP CUBIC(Linux默認),Compound TCP(windows默認),等等還有很多。不過,忽略它們的各具特色,擁塞控制和避免對核心性能的影響都是一樣的。

帶寬延遲乘積(Bandwith-Delay Product)

TCP內建的擁塞控制和擁塞避免機制對性能有另一個重要影響:發送方和接收方的接收窗口優化必須基於它們之間的RTT,目標速率的優化也如此。
爲了理解爲什麼這樣,首先,回想一下,發送者與接收者之間,未確認的,飛行數據的最大量是由接收窗口和擁塞窗口的最小值定義的:當前的接收窗口大小由每一個ACK通告,擁塞窗口大小,由發送者基於擁塞控制和避免算法動態調整。
如何發送者或接受者發送的數據超過了未確認數據的最大值,在繼續之前,它必須挺下來等待另一端對某些數據包的確認。要等待多久呢?由雙方之間的RTT決定。

帶寬延遲乘積
       數據鏈路容量與端到端延遲的乘積。結果就是在任意時刻處於飛行狀態的未被確認的數據量的最大值。

如果有一方頻繁的被強制停下來等待之前數據包的ACKs,就會產生數據量間隔(圖 2-7),這將限制連接所能達到的吞吐量。爲了解決這個問題,窗口大小應該被設置的足夠大,以便兩方都能夠在對一個更早的包的確認達到之前持續不斷的發送數據 -- 沒有間隔,達到最大的吞吐量。因此,最優的窗口大小依賴於RTT!選擇一個低的窗口尺寸,將會限制你的連接吞吐量,無論兩端的可用或通告帶寬有多大。



那麼流控制窗口(rwnd)和擁塞窗口(cwnd)的值應該設置爲多大呢?實際計算是很簡單的。首先,讓我們假設cwnd和rwnd的最小值是16KB,RTT是100ms:

                                  
無論發送者和接受者之間的可用帶寬是多少,該TCP連接的數據速率將不會超過1.31 Mbps!爲了獲得更高的吞吐量,我們需要提高最小窗口值或者降低RTT。
類似的,如果我們知道RTT和兩端之間的可用帶寬,我們也能夠計算出最優的窗口大小。在這個場景中,讓我們假設RTT保持不變(100ms),但發送者有10Mbps的可用帶寬,接收方位於高吞吐量100Mbps+的鏈路上。假定它們之間沒有網絡擁塞,我們的目標是充分利用10Mbps的鏈路:
                                     


窗口大小至少是122.1KB才能充分利用10Mbps的鏈路。記得如果沒有窗口擴大選項,TCP的最大接收窗口值是64KB -- 查閱“Window Scaling (RFC 1323)” -- 是時候檢查你的客戶端和服務端設置了!
好消息是,窗口大小的協商和調整是由網絡堆棧自動管理的。壞消息是有時候這也是TCP性能的限制因素。如果你很奇怪爲何你的連接只是用了可用帶寬的一部分來傳輸數據,而且你知道客戶端和服務端都能應該傳輸得更快的,那麼這可能就是由於小窗口尺寸引起的了:飽和的一方通告了一個低的接收窗口,糟糕的網絡環境和太多的包丟失重置了擁塞窗口,或者你的連接被限速了。

高速局域網的帶寬延遲乘積
BDP是RTT和目標速率的函數。因此,RTT不但是高傳播延遲網絡的常見瓶頸,而且也可能成爲本地局域網的瓶頸!
爲了在1msRTT的網絡上獲得1Gbit/s的速率,我們同樣也需要擁塞窗口至少爲122KB。計算方式如前所述。我們只是在目標速率後面添加幾個0,同時從RTT後面移走幾個0。

隊頭阻塞 (Head-of-line blocking)

TCP提供了運行在不可靠通道上的可靠網絡抽象,它包括了基本的包錯誤檢測與糾錯,按序發送,重傳丟失包,以及流控制,擁塞控制,和擁塞避免,這些機制都是爲了使網絡能夠最有效的運行。這些功能,集合在一起,使TCP成爲大部分應用首先的傳輸協議。
然而,儘管TCP廣受歡迎,但在有的場景下它不是唯一的,也不是必須的最好選擇。特別是,其中一些功能,比如有序和可靠的數據發送,不總是必須的,而且可能引入不必要的延遲和對性能的負面影響。
爲什麼會這樣呢?記得每一個被髮送的TCP包都有一個唯一的系列號,而且數據必須被按序傳輸到接收端(圖 2-8)。如果一個包在路由的過程中丟失了,那麼所有後續的包必須被存放在接收端的TCP緩衝中,知道丟失的包被重傳併到達接收端。因爲這個工作是在TCP層完成的,所以我們的應用程序看不到TCP重傳或包緩衝隊列,而且必須有了完整系列後才能訪問數據。當從socket讀取數據時,我們很容易觀察到延遲。這種效應被稱爲對頭阻塞。
對頭阻塞導致的延遲使我們的應用程序避免了包重組處理,也使我們的應用程序的代碼更簡單。不過,魚和熊掌不可兼得,這是以引入不可預期的包到達延遲爲代價的,通常稱之爲jitter,它會個應用程序性能代理負面影響。



此外,有的應用程序甚至不需要可靠傳輸或有序傳輸:如果每一個包都是一個單獨的消息,那麼按序發送就不是一定需要的,而且如果每一條消息都覆蓋所有之前的消息,那可靠傳輸就完全可以移除了。不幸的是,TCP不提供這種配置 -- 所有的包被系列化並按序發送。
能夠處理包亂序或者包丟失的應用程序,可能更適合使用另一個傳輸層協議,比如UDP。

TCP的優化

TCP是一種自適應協議,設計目標是所有網絡用戶能夠公平使用網絡,並且能夠做到最有校的利用當前網絡。因此,優化TCP的最好方法是,弄明白TCP是如何感知當前網絡狀況並基於下面層次的類型和上層的需要調整它的行爲:無線網絡可能需要不同的擁塞算法,爲了提供最好的用戶體驗,有的應用需要定製服務器質量(QoS)。
各種應用需求緊密相關,每一個TCP算法都有諸多可調選項,這使得TCP調整和優化成爲了一個永無止境的學術和商業研究領域。在本章中,我們只是蜻蜓點水式的瞭解下影響TCP性能的諸多因素。額外的機制,比如選擇性確認(SACK),延遲確認,快速重傳,還有很多其它機制,使得要理解,分析並調整每一個TCP會話都變得很複雜(或有趣,看你怎麼看了)。
但話說回來,雖然每一個算法的特定細節和反饋機制會不斷進化,但核心原則會保持不變:
  • TCP三次握手引入一個完整的RTT延遲
  • 每一個新的TCP連接都要經歷慢啓動
  • 所有的TCP連接通過TCP流控制和擁塞控制來調節吞吐量
  • TCP吞吐量由當前擁塞窗口大小決定
結果,在現代高速網絡中,一個TCP連接能夠達到的傳輸速率往往受發送者和接收者之間的RTT所限。此外,帶寬在不斷增長,但延遲受光速所限,沒有更多改善空間。在大多數情況下,TCP的瓶頸往往是延遲,而不是帶寬(圖 2-5)。

調整服務器配置

有幾十個可調的TCP參數,在你爲每一個緩衝和超時參數指定特定的值之前,首先把你的主機升級到最新版本是個明智的做法。TCP最佳實踐和影響它的性能的現行算法不斷的改進與完善,這些改進的大多數只在最新的內核中可用。簡而言之,爲了保證發送這和接收者TCP棧之間的最優交互,你應該保持更新你的服務器。

升級你的服務器版本,表面上,這看起來像個微不足道的建議。然而,實際中,這可能阻力重重:很多現行的服務器都針對特定內核版本做了優化,而且系統管理員也懶得去升級。
公平而言,每一次升級都有風險,但爲了獲得最好的性能,這可能也是你能做的最好的投資。
有了最新的內核之後,好的做法是配置你的服務器以使用如下的最佳實踐:
增加TCP初始擁塞窗口
        一個大的初始擁塞窗口允許TCP在第一次往返時傳輸更多數據,而且會顯著加快窗口增長 -- 這是對突發性短連接的一個特別關鍵的優化。
慢啓動重啓
        關閉空閒之後的慢啓動能夠提高TCP長連接的性能。
窗口伸縮
        打開窗口伸縮選項能夠增加接收窗口的最大值,以使高延遲連接獲得更好的性能。
TCP快速打開
       在特定場景下,允許在初始SYN包中發送應用數據。TFO是一個新的優化方案,它需要客戶端與服務器共同支持;檢查看你的應用程序是否可以利用。

通過這些設置,再加上最新的內核,就可以達到最好的性能 -- 更低的延遲和更高的吞吐量。
根據你的應用程序需要,你可能還需要調整服務器的其它TCP配置,做進一步的優化,以達到更好的連接率,更好的內存使用,或者其它類似的目標。這些配置與平臺、應用、硬件相關 -- 需要的時候查閱你的平臺文檔。

對於Linux用戶,ss是一個有用的強大工具,用於檢查已打開sockets的各種統計信息。在命令行,運行ss --option --extended --memory --processes --info,可以查看當前連接和它們各自的配置。

調整應用程序行爲

通過調整TCP的性能,應用程序和服務器的單個TCP連接能夠獲得最好的吞吐量和最小的延遲。然而,應用程序如何使用每一個新的,或已創建的TCP連接對性能也有重要影響,甚至更大。
  • 不發送數據纔是最快的;儘量少發數據
  • 我們不能使數據傳輸的更快,但我們可以使數據更近
  • TCP連接重用是提升性能的關鍵
消除不必要的數據傳輸,毫無疑問是最好的優化 -- 例如,消除不必要的資源,或通過採用合適的壓縮算法來保證傳輸的數據量最小。接着,通過分佈在全球各地的服務器,把數據放在離客戶端更近的地方 -- 例如,採用CDN -- 將有助於降低網絡延遲並顯著提升TCP性能。最後,儘可能的利用已存在的連接,以最大限度降低慢啓動和其它擁塞機制的開銷。

性能清單(checklist)

無論你的應用是什麼,優化TCP性能都能給每一個與你的服務器的連接帶來很多好處。下面是一個簡短的性能優化清單:
  • 升級服務器內核到最新的版本
  • 保證cwnd被設置爲10
  • 關閉空閒後慢啓動
  • 保證窗口伸縮選項是打開的
  • 消除冗餘數據傳輸
  • 對傳輸數據進行壓縮
  • 把服務器部署在離用戶近的地方以減少往返時間
  • 儘可能重用TCP連接

原文:http://chimera.labs.oreilly.com/books/1230000000545/ch02.html







發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章