第2章 TCP篇
html
互聯網的核心是兩個協議,IP和TCP。 IP也叫Internet協議,提供主機到主機的路由和尋址;TCP,傳輸控制協議,在不可靠的傳輸通道上提供一個可靠的網絡抽象。TCP / IP協議也一般被稱爲Internet協議套件,在1974年,它首次在一篇題爲《一個用於分組網絡互通的協議》的論文中被Vint Cerf和Bob Khan提出。web
最初的RFC建議(RFC 675)幾經修訂,在1981年發表TCP / IP V4正式規範,但分爲了兩個獨立的RFC:算法
從那時起,有一些加強建議補充到TCP協議中,但核心沒有大的改變。TCP迅速取代了之前的協議,成爲了如今許多最流行的應用程序的首選協議:萬維網,電子郵件,文件傳輸,和其餘許多應用。緩存
TCP在不可靠的傳輸信道上提供了可靠傳輸的抽象,隱藏了咱們的應用程序大部分的複雜性功能:丟包重傳,按序傳送,擁塞控制和避免,數據完整性,其餘特性。當您使用TCP流,TCP協議保證您的全部字節發送與接收的數據是相同的,他們會以相同的順序到達對端。TCP設計爲一個順序發送協議,而不是一個定時發送協議,這爲咱們Web性能優化帶來了很大的挑戰。安全
HTTP協議並無指定TCP爲惟一的傳輸協議。若是咱們須要,咱們徹底能夠基於數據報套接字(UDP)或任何其餘傳輸協議實現HTTP通訊,但在實際應用中,在互聯網上全部HTTP通訊都是經過TCP傳送。性能優化
正由於如此,瞭解TCP的一些核心機制是Web性能優化必不可少的知識。實際狀況是,你可能不會在應用中直接使用TCP套接字,但你在應用層選擇的設計方案,將影響您的應用程序在TCP層和底層網絡的性能。服務器
交錯的TCP和IP協議歷史cookie
咱們都熟悉IPv4和IPv6,但IPV {1,2,3,5}的狀況是咋樣的?IPv4中的4表明了TCP / IP協議的第四個版本,這是在1981年9月發佈的版本。最初的TCP / IP草案分裂成的兩個草案,到了V4版本,TCP/IP正式分裂兩成單獨的RFC。所以,IPv4中的V4只是一個版本繼承關係 - V4既不是比IPv1,IPv2,IPv3有更高的優先級,也不存在單獨的IPv1, IPv2,或IPv3協議。網絡
1994年,IETF工做組開始研究「互聯網下一代協議」(IPng)時,他們須要一個新的版本號,但V5已分配給另外一個實驗協議:聯網流協議(ST),所以他們選用了V6。事實證實,ST從未發展起來,這就是爲何不多有人據說過它。併發
三次握手
全部TCP鏈接開始前必須進行三次握手( 圖2-1 )。在任何應用中,客戶端或服務器在進行數據交換以前,他們必須先協商起始數據包序列號,其餘一些鏈接參數。出於安全考慮,雙方通常都是隨機挑選起始序列號。
圖2-1 三次握手
SYN 客戶端挑選一個隨機序列號x,同時發送一個SYN包,SYN包中也可能包括額外的TCP標誌和選項。 SYN ACK 服務端在x上增1 ,同時挑選一個服務端隨機序列號y ,附加服務端的一些標誌和選項,發送給客戶端 ACK 客戶端在x y基礎上都增長1,併發送一個ACK包服務器,完成了握手過程。
一旦三次握手完成後,應用程序數據能夠開始在客戶端和服務器之間傳輸。客戶端能夠在發送完ACK包後馬上發送數據包,但服務器必須在收到ACK以後才能發送任何數據。這個握手過程適用於每個TCP鏈接,並給全部使用TCP的網絡應用程序性能帶來了重要影響:任何應用程序在數據傳輸以前,新的TCP鏈接都將有一個固定的往返延遲。
例如,若是客戶端在紐約,服務端在倫敦,咱們經過光纖鏈路創建一個新的TCP鏈接,那麼三次握手將至少須要56毫秒( 表1-1 ):單向傳播須要28ms,往返一次須要56ms(譯者注:客戶端只要在收到SYN ACK後就能夠發送數據了)。須要注意的是,這和鏈接帶寬沒有直接關係。實際上,延遲是由客戶端和服務端的信號傳播延遲形成,也就是光信號的從紐約到倫敦的傳播時間。
建立新的TCP鏈接所須要的三次握手延遲是很明顯的,這也是重用TCP鏈接是任何應用程序的優化的關鍵手段的緣由之一。
TCP快速鏈接
TCP的三次握手已被肯定爲Web延遲的一個重要因素,在很大程度上也是因爲網頁瀏覽時一般須要訪問幾十個甚至上百個來自不經過主機的內容。
TCP快速建立(TFO)是一種旨在減小新的TCP鏈接延遲的機制。基於在谷歌完成的對流量分析和網絡模擬,研究人員已經證明經過TFO機制,容許在SYN包中攜帶數據,可下降HTTP網絡延遲了15%,總體頁面加載時間平都可以下降10%以上,並在某些高延遲的場景下,可下降高達40%。
在Linux 3.7 +內核中,客戶端和服務端目前都已可支持TFO,這將成爲客戶端和服務端的一個新選擇。儘管如此,TFO不是每個問題的解決方案。雖然它可能有助於消除三次握手來回的延遲,可是它只能應用與特定場景:SYN數據包的有效荷載大小有限制,只有某些類型的HTTP請求能夠被髮送;它僅適用於重複鏈接請求,由於每一個新的請求有加密cookie的要求。若是想對TFO的能力和限制行了詳細的瞭解,能夠查看最新的IETF草案「TCP Fast Open」。
擁塞避免和控制
1984年初,Jhon Nagle描述了「擁塞崩潰」場景,這在任何非對稱網絡中都有可能出現:
|
在複雜的網絡中,擁塞控制是一個公認的問題。咱們已經發現,國防部的互聯網協議(IP)-- 數據報協議,以及傳輸控制協議(TCP)--- 傳輸層協議,當把它們一塊兒使用時容易遭受不尋常的擁塞問題,這是由在傳輸層和數據報層之間的相互做用而引發的。特別的,IP網關對於被咱們稱爲「擁塞崩潰」的現象而言是脆弱的,特別是當這種網關連到大範圍的不一樣帶寬的網絡上的時候... 若是任何主機的數據包的傳輸往返時間超過了最大重傳閥值,該主機將開始發送愈來愈多重傳包到網絡中。這時網絡就處於一種麻煩狀態了。最終交換路由節點的緩衝區被塞滿,新的數據包必須被丟棄。數據包的往返時間超出了上限,主機上每一個數據包都要發送好幾回,最終這些重複包中的一部分到達了目的地。這就是擁塞崩潰。 這種狀況是持續的。一旦已達到擁塞點,若是丟棄算法是無優先級的,網絡將持續處於擁塞狀態。 |
|
|
- John Nagle - RFC 896 |
該報告指出,擁塞崩潰在ARPANET中不會成爲一個問題,由於大多數節點有統一的帶寬,骨幹網有很大的容量。然而,這些斷言早已變成了現實。在1986年,當網絡節點超過5000個時,且不一樣的網絡節點在持續增長時,一系列的擁塞崩潰事件橫掃整個網絡 - 在某些狀況下,網絡變得基本沒法使用。
爲了解決這些問題,多個機制被提出來用來控制擁塞:流量控制,擁塞控制和擁塞避免機制。
高級研究計劃署網絡(ARPANET)是現代互聯網的前身,也是世界上第一個業務分組交換網絡。該項目於1969年正式推出,並在1983年TCP / IP協議做爲主要的通訊協議取代了早期的NCP(網絡控制程序)。其他的,正如他們所說,都變成了歷史。
流量控制
流量控制是一種控制發送方過量發送數據給接收端的機制,- 接收端可能由於高負荷,處於繁忙狀態,也可能接收端只分配了固定大小的緩衝區。爲了解決這個問題,TCP鏈接的每一方都會宣告(圖2-2 )本身的接收窗口(rwnd值),用來知會對方本身的數據接收緩衝區大小。
圖2-2 接收窗口大小(RWND)
當鏈接首次創建時,雙方都採用系統的缺省設置值來初始化他們的rwnd,一個典型的Web頁面將緩存從服務器到客戶端的大部分數據流,客戶端的緩衝區就有可能成爲瓶頸。然而,若是客戶端傳輸流數據到服務器,例如在一個圖像或視頻的狀況下,大量的上傳數據,則服務器接收窗口可能成爲瓶頸。
若是出於任何緣由,一方若是不能處理,那麼它能夠更改爲一個較小的窗口,並通知對方。若是該窗口大小變爲零,則它被視爲一個信號:不要發送更多的數據,直到應用層處理完全部緩衝區中的數據。此更新過程在每一個TCP鏈接的整個生命週期中持續: 每一個ACK報文中攜帶最新的rwnd值,讓雙方能夠動態地調整數據流速和處理速度。
窗口縮放(RFC 1323)
最初的TCP規範分配了16 bit來設置接收窗口大小, 這意味着能夠設置的最大值爲 (216, or 65,535 bytes) . 事實證實,爲了得到最佳的性能,這個上限每每是不夠的,尤爲是具備高帶寬延延遲乘積「的網絡中 。
爲了解決這個問題,RFC 1323制定了一個「TCP窗口縮放」選項,這使咱們可以提升接收窗口大小從65,535字節到1G bits!窗口縮放選項在三次握手過程當中進行交換,在這個字段中攜帶了一個值,它用來表示後續ACK16bit須要左移的位數。
今天,TCP窗口縮放選項在全部主要平臺下默認狀況下都是啓用的。然而,中間節點,路由器和防火牆能夠重寫,或甚至清除此選項 - 若是您的服務端或者客戶端鏈接,沒法充分利用可用帶寬,能夠檢查一下接收窗口大小,這是一個好的開始。在Linux平臺下,窗口縮放設置能夠經過如下命令查看或者啓用:
慢啓動
儘管TCP存在流量控制機制,網絡擁塞崩潰依然在1980年代中後期成爲一個嚴峻的問題。問題是,前面提到的流量控制機制能夠防止發送端發送過量數據給接收端,可是沒有一個好的機制,能夠防止發送端仍是接收端侵蝕底層基礎網絡:在TCP鏈接創建的時候,不管發送方仍是接收方,都沒法清楚知道中間網絡的可用帶寬,所以咱們須要一種機制來評估它,並能使調整發送速度使其適應不斷變化的網絡條件。
咱們舉一個例子來講明這種調節機制是有價值的,想象一下你在家裏,正在播放一個在線視頻流,服務器以你家裏最大的下行帶寬下發數據,確保您的最佳體驗。這時,家庭網絡上的另外一個用戶打開一個新的鏈接,下載一些軟件更新。忽然之間,分配給視頻流可用的下行鏈路帶寬降低不少,在這種狀況下,視頻服務器必須調整其下發數據速率 - 若是它繼續以以前的速率,數據將堆積在中間的某個節點上,數據包將被丟棄,網絡使用效率極其低下。
1988年,Van Jacobson 和 Michael J. Karels發佈了一些算法來解決這些問題:慢啓動,擁塞避免,快速重傳和快速恢復。這四個算法迅速成爲了TCP強制性規範的一部分。事實上,人們廣泛認爲,正是這些更新算法使得在上世紀80年代和90年代初的流量以指數速度繼續增加狀況下,網絡避免了崩潰。
要了解慢啓動,最好是看它的實際操做。咱們再來看一下咱們在上一章提到的例子,假設客戶端在紐約,試圖從倫敦的服務器中下載一個文件。首先,客戶端和服務端進行三次握手,在此過程當中,雙方在ACK數據包中設置他們的各自的接收窗口(rwnd值)大小(圖2-2 )。一旦最後的ACK包發出,咱們就能夠開始交換數據了。
咱們要計算底層可用網絡帶寬的惟一方法就是在客戶端和服務端交換數據的過程當中進行逐步測量,這也是慢啓動算法要乾的事情。開始時,服務端爲每個新的TCP鏈接初始化一個擁塞窗口(cwnd的)值,cwnd的初始值通常是一個系統的缺省值(在Linux下是 initcwnd變量)。
擁塞窗口大小(cwnd) 表明服務端可以發送出去的但尚未收到ACK確認的最大數據報文段長度
cwnd變量不會在發送端和接收端之間進行交換 - 在這個例子中,cwnd是在倫敦的服務器維護一個私有變量。前面咱們討論過的rwnd,加上這裏的cwnd,TCP定義了一個規則:在客戶端和服務端之間未確認的數據包的最大值爲min(rwnd,cwnd)。到目前爲止,一切貌似都很好,但服務端和客戶端如何肯定擁塞窗口的最優值?畢竟,網絡條件隨時在變化,即便是相同網絡中的兩個節點之間,咱們也不想去手動調節cwnd值,若是有一種自動調整的算法將很是棒!
解決的辦法是先慢慢的發送,隨着數據包被確認,逐步增大窗口大小 -這就是慢啓動的核心思想!最初,cwnd的值設置爲一個TCP Segment,1999年4月的RFC 2581更新這個值至最多四個TCP Segment,2013年4月RFC 6928將這個值更新到了最大10個TCP Segment。
一個新的TCP鏈接創建後,每次能發送的最大未確認數據包大小是min(cwnd,rwnd),所以,服務端能夠發送4個TCP Segment到客戶端,而後他要等待數據包的ACK確認。每收到一個ACK,慢啓動算法定義發送端的cwnd值能夠增長一個TCP Segment - 這意味着每確認了一個數據包,兩個新的數據包能夠被髮送。這個階段一般被稱爲「指數增加」(圖2-3 )階段,客戶端和服務端都試圖快速地佔用他們之間可用的帶寬。
圖2-3 擁塞控制和擁塞避免
那麼,爲何慢啓動是咱們建立Web應用須要考慮的一個重要因素?由於HTTP和許多其餘應用協議運行在TCP之上,無論可用帶寬是多大,每個TCP鏈接必需要通過慢啓動階段 - 咱們不能一開始就使用全量的鏈路容量!
實際上,咱們開始用小擁塞窗口,在發送成功後窗口大小擴展一倍 - 即指數增加。其結果是,達到特定的閥值(客戶端和服務端之間的最大帶寬)所須要的時間與(公式2-1 )在客戶端和服務器之間的往返時間RTT,初始擁塞窗口大小的關係以下:
Equation 2-1. Time to reach the cwnd size of size N
\begin{aligned} \mathrm{Time} = \mathrm{RTT} \times \left\lceil log_2 \left( \frac{\mathrm{N}}{\mathrm{initial\ cwnd}} \right) \right\rceil \end{aligned}
讓咱們假設如下情形:
在這個例子中,咱們將使用舊的(RFC 2581)4個TCP Segments爲擁塞窗口的初始值,由於它仍然是目前大部分服務器最經常使用的值。經過下面例子的演示,你可能以爲你有必要去更新你的服務器了!
儘管接收窗口大小(rwnd)大小設置爲64 KB,可是根據咱們前面定義的,TCP鏈接創建後,初始的最大數據包大小爲min(rwnd,cwnd)。所以事實上,要達到64 KB的限制,咱們將須要擁塞窗口大小(cwnd)增加到45 TCP Segments,這將須要224毫秒:
\ [\begin{aligned} \frac{65,535\ \mathrm{bytes}}{1460\ \mathrm{bytes}} &\approx 45\ \mathrm{segments} \\ 56\ \mathrm{ms} \times \left\lceil log_2 \left( \frac{45}{4} \right) \right\rceil &= 224\ \mathrm{ms} \end{aligned} \]
須要4個往返RTT( 圖2-4 ),幾百毫秒的延遲,客戶端和服務器之間的吞吐量才能達到64 KB!所以事實上,對於一些客戶端和服務端能夠達到Mbps+的鏈接,慢啓動沒有任何效果。
圖2-4 擁塞窗口大小的增加
爲了減小擁塞窗口增加所須要的時間,咱們能夠減小客戶端和服務器之間的往返時間RTT - 例如在靠近客戶端的地方部署服務器。或者,咱們能夠增長初始擁塞窗口大小到新的RFC 9828值10 TCP Segments。
慢啓動對於大文件下載,流媒體下載來講不是問題,客戶端和服務端消耗幾百ms達到他們之間的最大速度 - 這個相對於總的下載時間來講,微不足道。
然而,對於許多HTTP鏈接來講,請求是短小且突發的,大部分狀況下,尚未達到最大閥值時,請求已經結束了。結果是,不少web應用的性能受到負向影響:由於慢啓動須要更長時間達到最大速率,尤爲對小請求來講,影響更大。
慢啓動重啓
除了調節新TCP鏈接的傳輸速率,TCP協議還實現了慢啓動的從新啓動機制(SSR),當一個鏈接空閒了一段時間後,將重置擁塞窗口(cwnd)。理由很簡單:當鏈接閒置的時候,網絡條件可能已經改變,爲避免擁塞,擁塞窗口復位到一個「安全」的默認值。
很顯然,SSR對那種應對突發請求的長TCP鏈接有很大的性能影響 - 例如HTTP的keep-alive請求。由於一旦被重置後,遇到突發請求時,又得經歷慢啓動的過程,而如上面描述,性能很低。因此在這種狀況下,建議在服務器上禁用SSR。在Linux平臺上的SSR設置能夠經過下面的命令進行查看和禁用SSR:
爲了說明三次握手和慢啓動階段對一個簡單HTTP的影響,讓咱們假設在紐約的客戶端向在倫敦的服務端請求一個20 KB的文件( 圖2-5 ),鏈接參數以下:
圖2-5 經過新的TCP鏈接獲取一個文件
0 ms |
客戶端開始TCP握手---SYN |
28 ms |
服務端響應SYN-ACK和指定其RWND的大小 |
56 ms |
客戶端發送ACK SYN-ACK,指定其RWND的大小,並當即發送HTTP GET請求 |
84 ms |
服務器接收到HTTP請求 |
124 ms |
服務器構建20 KB的響應,發送4 TCP Segments給客戶端,並暫停等待ACK確認(初始擁塞窗口大小爲4 TCP Segments) |
152 ms |
客戶端收到4個TCP Segments,每個TCP Segment發送一個ACK確認 |
180 ms |
服務端收到每一個ACK,cwnd遞增1,最終發送8個TCP Segments出去 |
208 ms |
客戶端收到8個 TCP Segments並ACK確認每個Segment |
236 ms |
服務器收到每一個ACK後,將CWnd增1,併發送剩餘的Segments |
264 ms |
客戶端收到剩餘的TCP Segments,並ACK確認每個Segment |
做爲一個練習,咱們將初始擁塞窗口設置爲10 TCP Segments圖2-5,咱們應該能夠發現能夠少一個完整的往返網絡延遲 - 在性能上提升了22%!
在一個RTT爲56ms的TCP鏈接上傳輸一個20KB的文件,將消耗掉264ms的時間。相比之下,讓咱們假設客戶端是可以重複使用同一個TCP鏈接(圖2-6 ),再次發出一樣的請求。
圖2-6 在現有的TCP鏈接上獲取一個文件
0 ms |
客戶端發送HTTP請求 |
28 ms |
服務端接收HTTP請求 |
68 ms |
服務端完成生成20 KB響應,目前擁塞窗口值已經大於發送文件所需的15 TCP Segments,所以它會一次性發送出全部數據。 |
96 ms |
客戶端接收全部15 TCP Segments,ACK確認每個Segment |
在同一鏈接上執行了一樣的請求,但如今沒有三次握手的成本和慢啓動階段的延遲,消耗的時間爲96ms,達到了275%的性能提高!
在上面兩個例子中,服務端和客戶端之間的5 Mbps的上行帶寬對性能沒有實質性影響,相反,傳播延時,和擁塞窗口大小是關鍵影響因素。
事實上,在同一個TCP鏈接上第一個請求和第二個請求之間的性能差距只與客戶端和服務端之間的RTT有關,你能夠嘗試着調整上面的參數試試看。
增長TCP的初始擁塞窗口(cwnd)
把服務端的初始擁塞窗口更新爲新的RFC 6928值(10 TCP Segments,簡稱IW10),是一個提高性能簡單的方法。這可讓全部用戶和全部基於TCP的應用受益。好消息是,許多操做系統在新的內核中已經更新了 - 你能夠檢查相應的文檔和發行說明。
對於Linux,全部2.6.39以上內核版本的缺省值都是IW10了。可是,不要停在那裏:升級到3.2 +還能夠獲得其餘好處,好比「按比例縮減(PRR)」 。
擁塞避免
咱們須要認識到,TCP設計時將丟包做爲一個性能的反饋,並用其來調節網速。換言之,它不是假設要發生丟包,而是當丟包發生時,會去調整。慢啓動初始化一個保守的擁塞窗口大小(cwnd),在後面,每個成功的確認,成倍的數據被髮送,直到它超過接收端的流量控制窗口大小(rwnd:系統配置的擁塞閾值窗口),或者當丟失了一個數據包,擁塞避免算法(圖2-3 )將開始起做用。
擁塞避免算法隱含的假設是一旦發生了網絡丟包 - 網絡路由的某處發生了擁塞,被迫丟棄該數據包,所以咱們須要調整咱們的窗口,以免誘發更多的網絡丟包。
一旦擁塞窗口被複位後,擁塞避免算法設定擁塞窗口增加算法,以儘可能避免進一步的丟包。在某一個時間點,丟包事件又發生了,這個過程會重複的執行一次。若是你曾經看過一個TCP鏈接的吞吐量跟蹤圖,你能夠觀察到有不少的鋸齒紋,如今你應該知道它爲何看起來這樣 - 這是擁塞控制和避免算法在不停的控制擁塞窗口的大小以免網絡丟包。
最後,值得注意的是,改善擁塞控制和擁塞避免的算法在學術研究和商業產品中都是一個活躍的領域,不一樣的網絡類型,不一樣類型的數據傳輸,有不一樣的優化算法。今天,在不一樣平臺上,可能運行着許多變種的算法:TCP Tahoe and Reno(原始實現),TCP Vegas,TCP New Reno,TCP BIC,TCPCUBIC(Linux上的默認實現),或 Compound TCP(Windows的默認實現),還有其餘不少種實現方案。然而,不管如何實現,都是爲了解決擁塞控制和擁塞問題。
快速恢復算法(PRR)
一旦發生了丟包,咱們須要復位擁塞窗口,但如何以最佳的方式恢復擁塞窗口是一個簡單的挑戰:若是你是過於激進,那麼間歇性的丟包將顯著影響整個鏈接的吞吐量,若是你調整的速度不夠快,那麼你可能丟失更多的包。
原本,TCP用「加增乘減」(AIMD)算法:當發生丟包,擁塞窗口大小減半,而後慢慢增長窗口大小。然而,在許多狀況下,AIMD算法是過於保守,所以新的算法被開發。
PRR是RFC 6937中指定的一種新的算法,其目標是當一個數據包丟失時,提升恢復速度。他到底優化了多少呢?根據谷歌開發新算法進行測量,它能夠減小3-10%的延遲。
PRR如今是Linux 3.2 +內核默認的擁塞避免算法 - 這也是一個很好的理由去升級你的服務器!
帶寬延遲乘積(BDP)
內置的TCP擁塞控制和擁塞避免機制包含了另一重要含義:最佳的發送方和接收窗口的大小(rwnd)必須根據它們之間的往返時間(RTT)和數據傳輸速率的不一樣而有所差別。
要理解爲何,咱們先回憶一下前面的結論:發送端和接收端之間未確認的最大數據包(沒有接收到ACK的包)大小爲min(cwnd,rwnd):rwnd將在每個ACK中更新,擁塞窗口大小(cwnd)則根據擁塞控制和避免算法的基礎上由發送者進行動態調整。
若是發送端或接收端未確認的數據包超過了上限,他們必須暫停發包,等待另外一端的ACK包,而後繼續發送。他們必須等待多久?這取決於它們之間的往返時間!
帶寬延遲乘積(BDP) 數據鏈路的容量和終端到終端的延遲的乘積。其結果是在任何一個時間點接收端和發送端之間最大的未確認數據包大小值。
若是發送端或接收端常常被迫停下來,等待之前包的ACK,那麼這將在數據流中造成空當( 圖2-7 ),這將限制鏈接的最大吞吐量。爲了解決這個問題,該窗口尺寸應該設計的足夠大,使得另外一端在前面的數據包ACK到達以前也能繼續發送數據, - 無間隙,才能達到最大的吞吐量。所以,最佳的窗口大小是依賴的往返時間(RTT)!選擇了一個較低的窗口大小,這將會限制鏈接的吞吐量,無論這兩端之間可用的或者標榜的帶寬有多大。
圖2-7 因爲低擁塞窗口致使的傳輸空當
那麼,rwnd和cwnd的值設置爲多大才是合適的呢?實際計算是很簡單。首先,咱們假設,最低的cwnd和RWND的窗口大小是16KB,之間的往返時間是100ms:
\ [\begin{aligned} 16\ \mathrm{KB} = (16 \times 1024 \times 8) &= 131,072\ \mathrm{bits}\\ \frac{131,072\ \mathrm{bits}}{0.1\ \mathrm{s}} &= 1,310,720\ \text{bits/s}\\ 1,310,720\ \text{bits/s} = \frac{1,310,720}{1,000,000} &= 1.31\ \mathrm{Mbps} \end{aligned} \]
不管發送端或接收端之間的可用帶寬是多少,這個TCP鏈接不會超過1.31 Mbps的數據傳輸速率!爲了實現更高的吞吐量,咱們須要提高的最小窗口大小,或者下降的往返時間(RTT)。
一樣地,若是咱們知道往返時間(RTT)和兩端的可用帶寬,咱們也能夠計算出最佳的窗口大小。在這種狀況下,讓咱們假定保持不變的往返時間(100ms),而發送方的可用帶寬爲10 Mbps,和接收器端擁有100 Mbps+的可用帶寬。假設它們之間沒有任何網絡擁塞,咱們的目標是,充分利用這個10 Mbps的連接:
\ [\begin{aligned} 10\ \mathrm{Mbps} = 10 \times 1,000,000 &= 10,000,000\ \mathrm{bits/s}\ \\ 10,000,000\ \text{bits/s} = \frac{10,000,000}{8 \times 1024} &= 1,221\ \text{KB/s}\\ 1,221\ \mathrm{KB/s} \times 0.1\ \mathrm{s} &= 122.1\ \mathrm{KB} \end{aligned} \]
窗口的大小至少須要122.1 KB,才能充分利用這10 Mbps的鏈路。前面咱們在流量控制章節中討論過,最大TCP接收窗口大小爲64 KB,除非「窗口縮放(RFC 1323)」是支持的-仔細檢查你的客戶端和服務端的設置!
好消息是,窗口大小協商和調整被網絡協議棧自動管理並進行相應的調整。壞消息是,有時它仍然是TCP性能的影響因素。若是你曾經碰到過下面的狀況,你的傳輸速度只佔用了可用帶寬的一小部分,甚至你知道不管是客戶端和服務端可以擁有更高的速率,你須要檢查一下是不是窗口設置太小:對端小的接收窗口,不穩定的網絡,高丟包致使的擁塞窗口復位,或顯式的流量模型都有可能影響到您的網絡吞吐量。
高速局域網中的帶寬延遲乘積
BDP是一個與往返時間(RTT)和數據速率相關的函數。雖然RTT多是高傳播延遲網絡中常見的瓶頸,可是RTT也可能成爲本地局域網上的瓶頸!
在1 ms RTT的局域網中,要達到1 Gbit / s速率,咱們須要一個至少122 KB的擁塞窗口。計算過程和上面是徹底同樣的,咱們簡單地在目標數據傳輸速率上增長几個0,在往返RTT中減小一樣的幾個0。
線頭阻塞
TCP提供了一個運行在不可靠信道上的可靠的網絡抽象,其中包括基本的數據包錯誤校驗和糾錯,按順序傳輸,重傳丟包,以及流量控制,擁塞控制和擁塞避免等提高網絡性能的特性。正是TCP擁有這些特色,使得TCP成爲大多數應用程序的首選傳輸協議。
然而,儘管TCP是一種廣泛的選擇,但不是惟一的,在某些場景下,也不必定是最佳的選擇。特別是,順序發送和可靠傳輸這樣的特性並不老是必要的,可能引發沒必要要的延誤和負向的性能影響。
要理解爲何,咱們記得每個TCP數據包帶有一個惟一的序列號,在傳輸時,數據必須按順序傳遞給接收端( 圖2-8 )。若是某個包在接收端的接收緩衝區中丟失,則全部後續數據包必須被放在接收端的緩衝區中,直到重傳包到達接收端。由於這個工做在TCP層完成,咱們的應用程序沒法感知底層的重傳,也沒法直接查看底層的緩衝區,應用程只有等待全部的數據包接收完畢後才能處理數據。相反,它只是看到從socket中讀不到數據或者讀取數據延遲,這種效應被稱爲TCP的線頭(HOL)阻塞。
圖2-8 TCP線頭阻塞
線頭阻塞雖然帶來了延遲,但它避免了咱們的應用程序去處理從新排序包和從新組裝包的工做,這使得咱們的應用程序代碼要簡單得多。然而,這帶來了數據包到達時間的不可預知性-一般稱爲抖動 -這對應用程序的性能產生了負向影響。
此外,一些應用程序可能根本不須要可靠傳輸,或者順序傳輸:若是每個數據包是一個獨立的消息,則按序傳輸是沒必要要的,若是每一個消息 將 覆蓋全部之前的消息,可靠傳傳輸也徹底沒有必要。不幸的是,TCP不提供這樣的配置 - 全部的包必須按序傳輸。
可以容忍和自行處理包的亂序或者丟包的應用,或者對延遲和抖動敏感的應用,能夠考慮另一個選擇:UDP。
丟包是能夠的
事實上,以得到最佳的性能從TCP,丟包是必要的,丟棄的數據包做爲一個網絡狀態反饋機制,接收端和發送端依據它來調整發送速率以免網絡擁塞,下降延遲-見「本地路由器的Bufferbloat」 。此外,一些應用程序容忍數據包丟失而不受影響:音頻,視頻和遊戲狀態更新等應用程序的數據不須要可靠和按序傳輸 - 順便說一句,這也是爲何WebRTC使用UDP做爲傳輸協議。
若是一個數據包丟失,音頻編解碼器能夠簡單的在音頻中插入一個小暫停,並繼續處理傳入的數據包。若是間隙足夠小,用戶可能根本察覺不到,而等待丟失的包可能帶來更多暫停的風險,這也將致使的更差的用戶體驗。
一樣,若是咱們在一個3D的世界去更新一個遊戲狀態,而後等待一個T-1時間點的數據包來描述其狀態時,這時咱們收到了時間T的狀態,這種狀況下T-1時間點的狀態就沒有必要了,咱們收到每一個消息後直接更新,能夠接收間歇性的丟包,這不影響遊戲的體驗。
優化TCP
TCP協議做爲一種自適應協議,設計的目標是全部網絡節點具備同等地位,有效的利用底層網絡。所以,優化TCP的最好方法就是讓TCP感知網絡條件和針對上層和下層的需求調整自身的行爲:無線網絡可能須要不一樣的擁塞算法,有些應用程序可能須要自定義的服務質量(QoS)的語義提供最佳體驗。
不一樣的應用需求和衆多的TCP優化算法相結合,使得TCP調整和優化算法成爲學術和商業研究一個無止境的領域。在本章中,咱們只是介紹了影響TCP性能的諸多因素的一些皮毛。還有一些其餘機制,如選擇性確認(SACK),延遲確認和快速重傳以及許多其餘機制,這些機制使得TCP會話理解,分析,調整起來更復雜(或有趣的,取決於你的觀點)。
雖說每一個算法和反饋機制的具體細節將在後續章節中詳細討論,其核心思想及其影響基本不變:
其結果是,在現代高帶寬網絡下,TCP鏈接的速率一般受限於發送端和接收端之間的傳播延遲。此外,雖然帶寬的不斷增長,可是傳播速度基本上限制在光速的一個很小的常數因子上了。在大多數狀況下,不是帶寬,而是時延,成爲TCP的主要瓶頸-參見圖2-5 。
服務器配置調整
做爲優化的一個起點,在調整緩衝區設置,幾十個TCP超時變量值前,把你的服務器升級到最新版本也許是最簡單有效的。TCP控制其性能的最佳實踐和底層優化算法在不斷髮展,這些變化多數包含在最新的內核中。總之,保持你的服務器更新到最新版本,以確保發送端和接收端的TCP鏈接最佳性能。
從表面上看,服務器內核版本升級彷佛是很小也很簡單的事情。然而,在實踐中,常常會碰到有重大阻力:現有的不少服務器都是基於某個內核版本開發的,許多系統管理員都不肯意冒着風險去執行升級。
一切都是公平的,每一次升級會帶來風險,可是,得到更好的TCP性能,這也是一個投資。就看你的去評估了。
若是你的內核已是最新了,你能夠按照下面的最佳實踐來調整你的服務器配置:
「增長TCP的初始擁塞窗口(cwnd)」 較大的起點擁塞窗口容許TCP在第一往返傳輸更多的數據,並能夠更快的增加窗口 - 這對那種突發和短請求居多的服務器來講,尤其關鍵。「慢啓動重啓動」 對於TCP長連接,禁止空閒後的慢啓動,能夠提高性能,特別是那種突發性請求居多的場景。「窗口縮放」(RFC 1323) 啓用窗口縮放增長最大接收窗口大小,並容許高延遲鏈接,以實現更好的吞吐量。「TCP快速打開」 容許在某些狀況下,在初始的SYN數據包發送應用程序數據。TFO是一種新的優化,這須要客戶端和服務器同時支持 -若是你的應用程序有須要,能夠考慮。
配合上述的配置和最新的內核能夠得到最佳的性能 - 更低的延遲和更高的吞吐量。
根據您的應用程序狀況,你可能還須要調整其餘的TCP服務器上的設置,以優化鏈接速率,內存消耗,或相似的指標。然而,這些配置設置都依賴於平臺,應用程序和硬件 - 須要參考平臺文檔。
對於Linux用戶, ss是一個強大的工具,能夠用來檢查打開的socket各類統計信息。在命令行中,執行「 ss --options --extended --memory --processes --info看到當前的各個端口以及各自的配置。
調整應用程序的行爲
調整TCP的性能能夠爲服務端和客戶端提供最佳的吞吐量和延遲。然而,應用程序如何使用每個新的或者已創建TCP鏈接,對性能也有很大的影響:
消除沒必要要的數據傳輸固然是最簡單有效的方法 - 例如,消除了沒必要要的資源,或者採用適當的壓縮算法確保最小的數據被傳輸。其次,咱們能夠經過在離客戶端更近的地方部署服務器 - 例如,使用一個CDN - 將有助於減小網絡往返延遲(RTT),並顯着提升TCP的性能。最後,在可能的狀況下,現有的TCP鏈接應該被重複使用,以最大限度地減小慢啓動和擁塞控制機制所帶來的開銷。
性能優化Checklist
高性能的TCP鏈接不管對哪一種應用,也不管是哪個接入到您服務器的鏈接來講,都是相當重要的。一個簡短的checklist: