因特網有兩個核心協議: 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
三次握手的步驟:服務器
三次握手帶來的延遲使得每建立一個新 TCP 鏈接都要付出很大代價,而這也決定了提升 TCP 應用性能的關鍵,在於想辦法重用鏈接。網絡
參考《TCP/IP 詳解》,可知 TCP 保證可靠傳輸的機制有:併發
下面詳細介紹流量控制及擁塞控制。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 通訊往返,這個過程帶來了較大的時間消耗。
爲了說明這個過程,這裏先介紹兩個概念:
並假設有以下條件:
計算可知,要達到 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 鏈接的數據傳輸速度,每每會受到接收端和發送端之間往返時間的限制。儘管帶寬不斷增加,但延遲依舊受限於光速,並且已經限定在了其最大值的一個很小的常數因子以內。所以,大多數狀況下,TCP 的瓶頸都是延遲,而非帶寬。
TCP 的最佳實踐以及影響其性能的底層算法一直在與時俱進,並且大多數變化都只在最新內核中才有實現。所以,讓你的服務器跟上時代是優化發送端和接收端 TCP 棧的首要措施。此外,能夠採起下列措施配置服務器:
參考:《Web 性能權威指南》