Web 性能之 TCP

TCP 簡介

因特網有兩個核心協議: IP 和 TCP。 IP,即 Internet Protocol(因特網協議),負責聯網主機之間的路由選擇和尋址; TCP,即 Transmission Control Protocol(傳輸控制協議),負責在不可靠的傳輸信道之上提供可靠的抽象層。 TCP/IP 也常被稱爲「因特網協議套件」。算法

咱們都知道有 IPv4 和 IPv6,那 IPv1~3 和 IPv5 呢?IPv4 中的 4 表示 TCP/IP 協議的第 4個版本,發佈於 1981 年 9 月。最初的 TCP/IP 建議中同時包含兩個協議,但標準草案第 4 版將這兩個協議分開,使之各自成爲獨立的 RFC。實際上, IPv4 中的 v4 只是代表了它與 TCP 前 3 個版本的承繼關係,以前並無單獨的 IPv一、 IPv2 或 IPv3 協議。1994 年,當工做組着手製定 Internet Protocol next generation(IPng)須要一個新版本號時, v5 已經被分配給了另外一個試驗性協議 Internet Stream Protocol(ST)。但ST 一直沒有什麼進展,這也是咱們爲何不多據說它的緣由。結果 TCP/IP 的下一版本就成了 IPv6。安全

三次握手

全部 TCP 鏈接一開始都要通過三次握手,客戶端與服務器在交換應用數據以前,必須就起始分組序列號,以及其餘一些鏈接相關的細節達成一致。出於安全考慮,序列號由兩端隨機生成。bash

三次握手

三次握手的步驟:服務器

  1. SYN。客戶端選擇一個隨機序列號 x,併發送一個 SYN 分組,其中可能還包括其餘 TCP 標誌和選項。
  2. SYN ACK。服務器給 x 加 1,並選擇本身的一個隨機序列號 y,追加本身的標誌和選項,而後返回響應。
  3. ACK。客戶端給 x 和 y 加 1 併發送握手期間的最後一個 ACK 分組

三次握手帶來的延遲使得每建立一個新 TCP 鏈接都要付出很大代價,而這也決定了提升 TCP 應用性能的關鍵,在於想辦法重用鏈接網絡

擁塞預防及控制

參考《TCP/IP 詳解》,可知 TCP 保證可靠傳輸的機制有:併發

  1. 分割數據塊。應用數據被分割成 TCP 認爲最適合發送的數據塊
  2. 自適應的超時及重傳策略。TCP 在發送時會設置一個定時器,若是時間到了尚未收到確認,它就重傳數據
  3. 中止等待。TCP 每發送完一個分組,就會中止發送,等待對方的確認,只有對方確認以後,才發送下一個分組
  4. 檢驗和。若是首部的檢驗和出錯,那麼 TCP 會從新丟棄該報文段,並要求發送端從新發送
  5. 從新排序。IP 數據報的到達順序可能會亂,所以 TCP 須要對收到的數據進行從新排序
  6. 丟棄重複的數據。IP 數據包可能會重複,所以 TCP 的接收端須要對重複的數據進行丟棄處理
  7. 流量控制。
  8. 擁塞控制。

下面詳細介紹流量控制及擁塞控制。tcp

流量控制

流量控制是一種預防發送端過多向接收端發送數據的機制。不然,接收端可能由於忙碌、負載重或緩衝區既定而沒法處理。爲實現流量控制, TCP 鏈接的每一方都要通告本身的接收窗口(rwnd),其中包含可以保存數據的緩衝區空間大小信息。性能

接收窗口

若是其中一端跟不上數據傳輸,那它能夠向發送端通告一個較小的窗口。假如窗口爲零,則意味着必須由應用層先清空緩衝區,才能再接收剩餘數據。這個過程貫穿於每一個 TCP 鏈接的整個生命週期:每一個 ACK 分組都會攜帶相應的最新 rwnd 值,以便兩端動態調整數據流速,使之適應發送端和接收端的容量及處理能力。優化

最初的 TCP 規範分配給通告窗口大小的字段是 16 位的,這至關於設定了發送端和接收端窗口的最大值(2^16 即 65 535 字節)。結果,在這個限制內常常沒法得到最優性能,特別是在那些「帶寬延遲積」(下面會介紹)很高的網絡中。爲解決這個問題, RFC 1323 提供了 **TCP 窗口縮放(TCP Window Scaling) ** 選項,能夠把接收窗口大小由 65 535 字節提升到 1G 字節!縮放 TCP 窗口是在三次握手期間完成的,其中有一個值表示在未來的 ACK 中左移 16 位窗口字段的位數。今天, TCP 窗口縮放機制在全部主要平臺上都是默認啓用的。不過,中間節點和路由器能夠重寫,甚至徹底去掉這個選項。若是你的服務器或客戶端的鏈接不能徹底利用現有帶寬,那每每該先查一查窗口大小。在 Linux 中,能夠經過以下命令檢查和啓用窗口縮放選項:spa

$> sysctl net.ipv4.tcp_window_scaling
$> sysctl -w net.ipv4.tcp_window_scaling=1
複製代碼

慢啓動

儘管流量控制確實能夠防止發送端向接收端過多發送數據,但卻沒有機制預防任何一端向潛在網絡過多發送數據。換句話說,發送端和接收端在鏈接創建之初,誰也不知道可用帶寬是多少,所以須要一個估算機制,而後還要根據網絡中不斷變化的條件而動態改變速度。

解決這個問題的算法有:慢啓動、擁塞預防、快速重傳和快速恢復

慢啓動算法的設計思路是這樣的:服務器經過 TCP 鏈接初始化一個新的**擁塞窗口(cwnd)**變量,將其值設置爲一個系統設定的保守值(在 Linux 中就是 initcwnd)。客戶端與服務器之間最大能夠傳輸(未經 ACK 確認的)數據量取 rwnd 和 cwnd 變量中的最小值。而後在分組被確認後增大窗口大小,慢慢地啓動(下圖前半部分)——慢啓動中的「慢」指的不是窗口增加的速度慢,而是由於要增加到適合當前帶寬的窗口大小,須要屢次 TCP 通訊往返,這個過程帶來了較大的時間消耗。

擁塞控制和擁塞預防

爲了說明這個過程,這裏先介紹兩個概念:

  1. MTU,Maximum Transmit Unit,最大傳輸單元,即物理接口(數據鏈路層、IP 層)提供給其上層最大一次傳輸數據的大小,默認爲 1500 Byte
  2. MSS,Maximum Segment Size ,最大TCP分段大小,默認爲 1500 - 20(TCP 首部)- 20(IP 首部) = 1460 字節

並假設有以下條件:

  1. 客戶端和服務器的接收窗口爲 65 535 字節(64 KB)
  2. 初始的擁塞窗口: 4 段(MSS)
  3. 往返時間是 56 ms(倫敦到紐約)

計算可知,要達到 64 KB 的限制,須要把擁塞窗口大小增長到 45 段,而這須要 224 ms:

也就是說,要達到客戶端與服務器之間 64 KB 的吞吐量,須要 4 次往返,幾百 ms 的延遲!至於客戶端與服務器之間實際的鏈接速率是否是在 Mbit/s 級別,絲絕不影響這個結果。這就是慢啓動。

慢啓動致使客戶端與服務器之間通過幾百 ms 才能達到接近最大速度的問題,對於大型流式下載服務的影響倒不顯著,由於慢啓動的時間能夠分攤到整個傳輸週期內消化掉。但是,對於不少 HTTP 鏈接,特別是一些短暫、突發的鏈接而言,經常會出現尚未達到最大窗口請求就被終止的狀況。換句話說,不少 Web 應用的性能常常受到服務器與客戶端之間往返時間的制約。由於慢啓動限制了可用的吞吐量,而這對於小文件傳輸很是不利。

所以,把服務器的初始 cwnd 值增大到 RFC 6928 新規定的 10 段(IW10),是提高用戶體驗以及全部 TCP 應用性能的最簡單方式。好消息是,不少操做系統已經更新了內核,採用了增大後的值。

另外,除了調節新鏈接的傳輸速度, TCP 還實現了 SSR(Slow-Start Restart,慢啓動重啓)機制。這種機制會在鏈接空閒必定時間後重置鏈接的擁塞窗口。道理很簡單,在鏈接空閒的同時,網絡情況也可能發生了變化,爲了不擁塞,理應將擁塞窗口重置回「安全的」默認值。毫無疑問, SSR 對於那些會出現突發空閒的長週期 TCP 鏈接(好比 HTTP 的 keep-alive 鏈接)有很大的影響。所以,建議在服務器上禁用 SSR。

擁塞預防

擁塞預防算法,其實就是上圖(圖2-3)的後半部分。

慢啓動以保守的窗口初始化鏈接,隨後的每次往返都會成倍提升傳輸的數據量,直到超過接收端的流量控制窗口,或者有分組丟失爲止,此時擁塞預防算法介入。擁塞預防算法把丟包做爲網絡擁塞的標誌,即路徑中某個鏈接或路由器已經擁堵了,以致於必須採起刪包措施。所以,必須調整窗口小,以免形成更多的包丟失,從而保證網絡暢通。重置擁塞窗口後,擁塞預防機制按照本身的算法來增大窗口以儘可能避免丟包。某個時刻,可能又會有包丟失,因而這個過程再從頭開始。

肯定丟包恢復的最優方式並不容易。若是太激進,那麼間歇性的丟包就會對整個鏈接的吞吐量形成很大影響。而若是不夠快,那麼還會繼續形成更多分組丟失。

最初, TCP 使用 AIMD( Multiplicative Decrease and Additive Increase,倍減加增)算法,即發生丟包時,先將擁塞窗口減半,而後每次往返再緩慢地給窗口增長一個固定的值。不過,不少時候 AIMD 算法太過保守,所以又有了新的算法。

PRR( Proportional Rate Reduction,比例降速)就是 RFC 6937 規定的一個新算法,其目標就是改進丟包後的恢復速度。改進效果如何呢?根據谷歌的測量,實現新算法後,因丟包形成的平均鏈接延遲減小了 3%~10%。

快速重傳和快速恢復

簡單介紹一下這兩個算法:若是一連串收到3個或3個以上的重複ACK,就很是多是一個報文段丟失了。因而咱們就重傳丟失的數據報文段,而無需等待超時定時器溢出。這就是快速重傳算法。接下來執行的不是慢啓動算法而是擁塞避免算法。這就是快速恢復算法。

帶寬延遲積

BDP(Bandwidth-delay product,帶寬延遲積):數據鏈路的容量與其端到端延遲的乘積。

在 TCP 通訊中,發送端或接收端不管誰被迫頻繁地中止等待以前分組的 ACK,都會形成數據缺口,從而必然限制鏈接的最大吞吐量。爲解決這個問題,應該讓窗口足夠大,以保證任何一端都能在 ACK 返回前持續發送數據。只有傳輸不中斷,才能保證最大吞吐量。而最優窗口大小取決於往返時間!不管實際或通告的帶寬是多大,窗口太小都會限制鏈接的吞吐量。

那麼,流量控制窗口( rwnd)和擁塞控制窗口( cwnd)的值多大合適呢?實際上,計算過程很簡單。首先,假設 cwnd 和 rwnd 的最小值爲 16 KB,往返時間爲 100 ms,那麼:

所以,無論發送端和接收端的實際帶寬多大,這個 TCP 鏈接的數據傳輸速率不會超過 1.31Mbit/s !想提升吞吐量,要麼增大最小窗口值,要麼減小往返時間。

相似地,知道往返時間和兩端的實際帶寬也能夠計算最優窗口大小。這一次咱們假設往返時間不變(仍是 100 ms),發送端的可用帶寬爲 10 Mbit/s,接收端則爲100 Mbit/s+。還假設兩端之間沒有網絡擁塞,咱們的目標就是充分利用客戶端的 10Mbit/s 帶寬:

所以,窗口至少須要 122.1 KB(這個值就是帶寬延遲積) 才能充分利用 10 Mbit/s 帶寬!

隊首阻塞

每一個 TCP 分組都會帶着一個惟一的序列號被髮出,而全部分組必須按順序傳送到接收端。若是中途有一個分組沒能到達接收端,那麼後續分組必須保存在接收端的 TCP 緩衝區,等待丟失的分組重發併到達接收端。這一切都發生在 TCP 層,應用程序對 TCP 重發和緩衝區中排隊的分組一無所知,必須等待分組所有到達才能訪問數據。在此以前,應用程序只能在經過套接字讀數據時感受到延遲交付。這種效應稱爲** TCP 的隊首阻塞**。

隊首阻塞形成的延遲可讓咱們的應用程序不用關心分組重排和重組,從而讓代碼保持簡潔。然而,代碼簡潔也要付出代價,那就是分組到達時間會存在沒法預知的延遲變化。這個時間變化一般被稱爲抖動,也是影響應用程序性能的一個主要因素

有些應用程序可能並不須要可靠的交付或者不須要按順序交付。好比,每一個分組都是獨立的消息,那麼按順序交付就沒有任何須要。並且,若是每一個消息都會覆蓋以前的消息,那麼可靠交付一樣也沒有必要了。無需按序交付數據或可以處理分組丟失的應用程序,以及對延遲或抖動要求很高的應用程序,最好選擇 UDP 等協議

針對 TCP 的優化建議

由上文可知,TCP 的核心原理及其影響有:

  1. TCP 三次握手增長了整整一次往返時間
  2. TCP 慢啓動將被應用到每一個新鏈接
  3. TCP 流量及擁塞控制會影響全部鏈接的吞吐量
  4. TCP 的吞吐量由當前擁塞窗口大小控制

現代高速網絡中 TCP 鏈接的數據傳輸速度,每每會受到接收端和發送端之間往返時間的限制。儘管帶寬不斷增加,但延遲依舊受限於光速,並且已經限定在了其最大值的一個很小的常數因子以內。所以,大多數狀況下,TCP 的瓶頸都是延遲,而非帶寬

服務器配置調優

TCP 的最佳實踐以及影響其性能的底層算法一直在與時俱進,並且大多數變化都只在最新內核中才有實現。所以,讓你的服務器跟上時代是優化發送端和接收端 TCP 棧的首要措施。此外,能夠採起下列措施配置服務器:

  1. 增大TCP的初始擁塞窗口
  2. 禁用慢啓動重啓
  3. 啓用窗口縮放
  4. TCP 快速打開(TFO)

應用程序行爲調優

  1. 消除沒必要要的數據傳輸。好比,減小下載沒必要要的資源,或者經過壓縮算法把要發送的比特數降到最低。
  2. 部署 CDN。經過在不一樣的地區部署服務器(好比,使用 CDN),把數據放到接近客戶端的地方,能夠減小網絡往返的延遲,從而顯著提高 TCP 性能。
  3. 儘量重用已經創建的 TCP 鏈接,把慢啓動和其餘擁塞控制機制的影響降到最低

性能檢查清單

  1. 把服務器內核升級到最新版本
  2. 增大TCP的初始擁塞窗口(cwnd.大小
  3. 禁用慢啓動重啓
  4. 啓用窗口縮放
  5. 減小傳輸冗餘數據
  6. 壓縮要傳輸的數據
  7. 把服務器放到離用戶近的地方以減小往返時間
  8. 盡最大可能重用已經創建的 TCP 鏈接

參考:《Web 性能權威指南》

相關文章
相關標籤/搜索