TCP性能優化實戰

語言: CN / TW / HK

本次和大家聊一下TCP性能優化。

TCP全稱為Transmission Control Protocol,每一個IT人士對TCP都有一定了解。TCP協議屬於底層協議,對於大部分研發人員來説,這是透明的,無需關心TCP的實現與細節。

不過如果想做深入的性能優化,TCP是繞不過去的一環。要講TCP性能優化,必須先回顧一下TCP的一些細節。讓我們先來看一下TCP的首部格式

TCP報文段的首部格式

TCP報文段首部的前20個字節是固定的,後面有4n字節是根據需要而增加的選項(n是整數)。因此TCP首部的最小長度是20字節。

  • 序號:字段值指的是本報文段所發送的數據的第一個字節的序號
  • 確認號:是期望收到對方下一個報文段的第一個數據字節的序號。若確認號為= N,則表明:到序號N-1為止的所有數據都已正確收到
  • ACK: 僅當ACK = 1時確認號字段才有效,當ACK = 0時確認號無效。TCP規定,在連接建立後所有的傳送的報文段都必須把ACK置為1
  • SYN: 在連接建立時用來同步序號。當SYN=1而ACK=0時,表明這是一個連接請求報文段。對方若同意建立連接,則應在響應的報文段中使SYN=1和ACK=1,因此SYN置為1就表示這是一個連接請求或連接接受報文
  • 窗口:窗口字段明確指出了現在允許對方發送的數據量。窗口值經常在動態變化。
  • 選項:
    • 最大報文段長度MSS:
      • 以太網Ethernet最大的數據幀是1518字節。以太網幀的幀頭14字節和幀尾CRC校驗4字節(共佔18字節),剩下承載上層協議的地方也就是Data域最大就只剩1500字節. 這個值我們就把它稱之為MTU。
      • 為了達到最佳的傳輸效能TCP協議在建立連接的時候通常要協商雙方的MSS值,這個值TCP協議在實現的時候往往用MTU值代替,MSS一般在1420~1460,1460是由1500 - 20(IP頭)- 20/60(TCP頭)計算出的。
    • 窗口擴大選項:TCP首部中窗口字段長度是16位,因此最大的窗口大小為64K字節。可以將窗口最大值增大到2^(16+14)-1=2^30-1

三次握手

原理

所有TCP連接一開始都要經過三次握手,如下圖所示:

img

  • SYN: 客户端選擇一個隨機序列號x,併發送一個SYN分組,其中可能還包括其他TCP標誌和選項。
  • SYN ACK: 服務器給x加1,並選擇自己的一個隨機序列號y,追加自己的標誌和選項,然後返回響應。
  • ACK: 客户端給x和y加1併發送握手期間的最後一個ACK分組。

上面的內容我們在書上看過多次,這次我們用wireshark抓包看一下詳情:

本機ip為192.168.1.102,服務器ip為122.51.162

sync

sync ack

ack

三次握手完成後,客户端與服務器之間就可以通信了。

這個啟動通信的過程適用於所有TCP連接,因此對所有使用TCP的應用具有非常大的性能影響,因為每次傳輸應用數據之前,都必須經歷一次完整的往返。

優化

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

TCP快速打開

TFO(TCP fast open)允許服務器和客户端在連接建立握手階段交換數據,從而使應用節省了一個RTT的時延。

但是TFO會引起一些問題,因此協議要求TCP實現必須默認禁止TFO。需要在某個服務端口上啟用TFO功能的時候需要應用程序顯示啟用。

設置:sysctl -n net.ipv4.tcp_fastopen = 0x203

限制:並不能解決所有問題,它雖然有助於減少三次握手的往返時間,但卻只能在某些情況下有效,如隨同SYN分組一起發送的數據淨荷有最大尺寸限制、只能發送某些類型的HTTP請求,以及由於依賴加密cookie,只能應用於重複的連接。

效果:經過流量分析和網絡模擬,谷歌研究人員發現TFO平均可以降低HTTP事務網絡延遲15%、整個頁面加載時間10%以上。在某些延遲很長的情況下,降低幅度甚至可達40%。

盡最大可能重用已經建立的TCP連接

長鏈接(Keep-Alive)

Keep-Alive,HTTP 1.1 之後默認開啟,指在一個 TCP 連接中可以持續發送多份數據而不會斷開連接

Keep-Alive能夠實現,需要服務端支持:

Httpd守護進程,如nginx需要設置keepalive_timeout

  • keepalive_timeout=0:建立tcp連接 + 傳送http請求 + 執行時間 + 傳送http響應 + 關閉tcp連接 + 2MSL
  • keepalive_timeout>0:建立tcp連接 + (最後一個響應時間 – 第一個請求時間) + 關閉tcp連接 + 2MSL

另外TCP自身也有Keep-Alive,是檢測TCP連接狀況的保鮮機制

  • net.ipv4.tcpkeepalivetime:表示TCP鏈接在多少秒之後沒有數據報文傳輸啟動探測報文

  • net.ipv4.tcpkeepaliveintvl:前一個探測報文和後一個探測報文之間的時間間隔

  • net.ipv4.tcpkeepaliveprobes:探測的次數

負載均衡

基本原理:客户端(如:ClientA)與負載均衡設備之間進行三次握手併發送 HTTP 請求。負載均衡設備收到請求後,會檢測服務器是否存在空閒的長鏈接,如果不存在,服務器將建立一個新連接。當 HTTP 請求響應完成後,客户端與負載均衡設備協商關閉連接,而負載均衡則保持與服務器之間的這個連接。當有其他客户端(如:ClientB)需要發送 HTTP 請求時,負載均衡設備會直接向服務器之間保持的這個空閒連接發送 HTTP 請求,避免來由於新建 TCP 連接造成的延時和服務器資源耗費。

接收窗口rwnd

流量控制是一種預防發送端過多向接收端發送數據的機制。否則,接收端可能因為忙碌、負載重或緩衝區容量有限而無法處理。為實現流量控制,

TCP連接的每一方都要通吿自己的接收窗口(rwnd),其中包含能夠保存數據的緩衝區空間大小信息。

img

第一次建立連接時,兩端都會使用自身系統的默認設置來發送rwnd。每個ACK分組都會攜帶相應的最新rwnd值,以便兩端動態調整數據流速,使之適應發送端和接收端的容量及處理能力。

最初的TCP規範分配給通吿窗口大小的字段是16位的,這相當於設定了發送端和接收端窗口的最大值(2的16次方即65 535字節)。為解決這個問題,RFC 1323提供了“TCP窗口縮放”(TCPWindow Scaling)選項,可以把接收窗口大小由65 535字節提高到1G字節!

縮放TCP窗口是在三次握手期間完成的,其中有一個值表示在將來的ACK中左移16位窗口字段的位數。

優化

客户端與服務器之間最大可以傳輸數據量取rwnd和cwnd變量中的最小值。

開啟窗口縮放

開啟窗口縮放,能使接收窗口大小從2^16升級到2^30,可以獲得更好的傳輸性能。

查看:sysctl net.ipv4.tcp_window_scaling

設置:sysctl -w net.ipv4.tcp_window_scaling=1

效果:比起不開啟窗口縮放,能夠充分利用帶寬

這裏講述一下帶寬延遲積。BDP(Bandwidth-delay product,帶寬延遲積)數據鏈路的容量與其端到端延遲的乘積。這個結果就是任意時刻處於在途未確認狀態的最大數據量。

發送端或接收端無論誰被迫頻繁地停止等待之前分組的ACK,都會造成數據缺口,從而必然限制連接的最大吞吐量。

無論實際或通吿的帶寬是多大,窗口過小都會限制連接的吞吐量。

知道往返時間和兩端的實際帶寬也可以計算最優窗口大小。這一次我們假設往返時間為100 ms,發送端的可用帶寬為10 Mbps,接收端則為100 Mbps+。還假設兩端之間沒有網絡擁塞,我們的目標就是充分利用客户端的10 Mbps帶寬:

img

窗口至少需要122.1 KB才能充分利用10 Mbps帶寬!如果沒“窗口縮放,TCP接收窗口最大隻有64 KB,無論網絡性能有多好,永遠無法充分利用帶寬。

慢啟動與擁塞避免

接收窗口對性能很重要,但擁塞窗口比接收窗口更重要。

客户端與服務器之間最大可以傳輸(未經ACK確認的)數據量取rwnd和cwnd變量中的最小值,而一開始的cwnd很小,通過慢啟動算法不斷增大。

慢啟動和擁塞避免的算法有很多,這裏使用Tahoe版本的TCP版本進行展示,這個也是帶有擁塞控制功能的第一個TCP版本,使用的擁塞避免算法為AIMD(Multiplicative Decrease and Additive Increase,倍減加增)。

image.png

  • SS:Slow Start,慢啟動階段。TCP 剛開始傳輸的時候,速度是慢慢漲起來的,除非遇到丟包,否則速度會一直指數性增長。
  • CA:Congestion Avoid,擁塞避免階段。當擁塞窗口大於ssthresh後, CWND增長速度會下降,不再像 SS 那樣指數增,而是線性增。
  • 超時:當數據發送方感知到丟包時,會記錄此時的 CWND,並計算合理的 ssthresh 值,一般ssthresh會置為超時時CWND的一半,發送端會驟降 CWND 到最初始的狀態,當 CWND 重新由小至大增長,直到 sshtresh 時,不再 SS 而是 CA

服務器會有一個默認cwnd初始值。最初,cwnd的值只有1個TCP段。1999年4月,RFC 2581將其增加到了4個TCP段。2013年4月,RFC 6928再次將其提高到10個TCP段。

計算題

問題:cwnd大小達到N所需的時間

下面我們就來看一個例子,假設:

• 客户端和服務器的接收窗口為65535字節(64 KB);

• 初始的擁塞窗口:4段(RFC 2581);

• 往返時間是56 ms(倫敦到紐約);

這個例子説明網絡正常情況下,要達到最大傳輸量,需要224ms。因為慢啟動限制了可用的吞吐量,而這對於小文件傳輸非常不利,因為擁塞控制尚處於slowstart階段,傳輸就完畢了。

優化

確保cwnd大小為10

查看

  1. 寫腳本

    probe kernel.function("tcp_init_cwnd").return

    {

    printf("tcp_init_cwnd return: %d\n", $return)

    }

  2. 把服務器內核升級到最新版本(Linux:3.2+)

增大TCP的初始擁塞窗口

設置:在內核中增加一個控制initcwnd的proc參數,/proc/sys/net/ipv4/tcp_initcwnd。該方法對所有的TCP連接有效。

限制:初始擁塞窗口不能設置特別大,否則會導致交換節點的緩衝區被填滿,多出來的分組必須刪掉,相應的主機會在網絡中製造越來越多的數據報副本,使得整個網絡陷入癱瘓。行業內各大cdn廠商都調整過init_cwnd值,普遍取值在10-20之間

效果

禁用慢啟動重啟

名詞解釋:SSR(Slow-Start Restart,慢啟動重啟)會在連接空閒一定時間後重置連接的擁塞窗口。

原因:在連接空閒的同時,網絡狀況也可能發生了變化,為了避免擁塞,理應將擁塞窗口重置回“安全的”默認值。

查看: sysctl net.ipv4.tcp_slow_start_after_idle

設置: sysctl -w net.ipv4.tcp_slow_start_after_idle=0

效果:對於那些會出現突發空閒的長週期TCP連接(比如HTTP的keep-alive連接)有很大的影響,具體提升性能根據網絡性能和數據量大小不同而不同

更改擁塞避免算法

擁塞控制算法對TCP性能影響很大,除了上面提到的AIMD算法,還有眾多其他算法。

PRR(Proportional Rate Reduction,比例降速)就是RFC 6937規定的一個新算法,其目標就是改進丟包後的恢復速度。

效果:根據谷歌的測量,實現新算法後,因丟包造成的平均連接延遲減少了3%~10%。

設置:升級服務器。PRR現在是Linux 3.2+內核默認的擁塞預防算法。

減少傳輸數據量

方案

  1. 減少傳輸宂餘數據

  2. 壓縮要傳輸的數據:gzip、protobuf、webp等

  3. 再快也快不過什麼也不用發送

減少往返時間

方案

  1. 多機房部署服務器
  2. 使用CDN

隊首阻塞

隊首(HOL,Head of Line)阻塞:如果中途有一個分組沒能到達接收端,那麼後續分組必須保存在接收端的TCP緩衝區,等待丟失的分組重發併到達接收端。這一切都發生在TCP層,應用程序對TCP重發和緩衝區中排隊的分組一無所知,必須等待分組全部到達才能訪問數據。在此之前,應用程序只能在通過套接字讀數據時感覺到延遲交付。

img

優點:應用程序不用關心分組重排和重組,從而讓代碼保持簡潔。

缺點:分組到達時間會存在無法預知的延遲變化。這個時間變化通常被稱為抖動,也是影響應用程序性能的一個主要因素。

優化

UDP

無法優化,這是TCP的基礎邏輯,目前沒有優化的可能。

無需按序交付數據或能夠處理分組丟失的應用程序,以及對延遲或抖動要求很高的應用程序,最好選擇UDP等協議。

一般的音頻或者遊戲等應用,可以選擇使用UDP協議

總結

針對TCP的優化建議

  1. 服務器配置調優
    • 服務器使用最新版本
    • 增大TCP的初始擁塞窗口
    • 慢啟動重啟
    • 窗口縮放(RFC 1323)
    • TCP快速打開
    • 可用ss命令或sysctl -a | grep tcp查看相關配置
  2. 應用程序行為調優
    • 再快也快不過什麼也不用發送,能少發就少發
    • 我們不能讓數據傳輸得更快,但可以讓它們傳輸的距離更短
    • 重用TCP連接是提升性能的關鍵
  3. 性能檢查清單
    • 把服務器內核升級到最新版本(Linux:3.2+);
    • 確保cwnd大小為10;
    • 禁用空閒後的慢啟動;
    • 確保啟動窗口縮放;
    • 減少傳輸宂餘數據;
    • 壓縮要傳輸的數據;
    • 把服務器放到離用户近的地方以減少往返時間;
    • 盡最大可能重用已經建立的TCP連接。

資料

  1. Web權威性能指南
  2. TCP 滑動窗口 與窗口縮放因子
  3. TCP的滑動窗口與擁塞窗口
  4. Web 性能優化 - TCP
  5. 就是要你懂TCP--性能優化大全
  6. TCP報文段的首部格式
  7. TCP Socket通信詳細過程
  8. TCP三次握手以及SYN,ACK,Seq的不詳細解釋
  9. Wireshark數據包分析
  10. Wireshark網絡分析就這麼簡單
  11. TCP-fastopen(TFO)
  12. TCP系列40—擁塞控制—3、慢啟動和擁塞避免概述
  13. TCP系列41—擁塞控制—4、Linux中的慢啟動和擁塞避免(一)
  14. HTTP Keep-Alive是什麼?如何工作?(理解TCP生命週期)
  15. nginx - KeepAlive詳細解釋

最後

大家如果喜歡我的文章,可以關注我的公眾號(程序員麻辣燙)

往期文章回顧:

技術

  1. TCP性能優化
  2. 限流實現1
  3. Redis實現分佈式鎖
  4. Golang源碼BUG追查
  5. 事務原子性、一致性、持久性的實現原理
  6. CDN請求過程詳解
  7. 記博客服務被壓垮的歷程
  8. 常用緩存技巧
  9. 如何高效對接第三方支付
  10. Gin框架簡潔版
  11. InnoDB鎖與事務簡析

讀書筆記

  1. 如何鍛鍊自己的記憶力
  2. 簡單的邏輯學-讀後感
  3. 熱風-讀後感
  4. 論語-讀後感

思考

  1. 對項目管理的一些看法
  2. 對產品經理的一些思考
  3. 關於程序員職業發展的思考
  4. 關於代碼review的思考
  5. Markdown編輯器推薦-typora