1. 程式人生 > >Tcp效能調優 解決Tcp長延時

Tcp效能調優 解決Tcp長延時

背景:

根據Tcp的理論計算,Tcp最佳狀態下傳輸是流水並行的,傳輸時間等於傳輸資料耗時+TTL,即千兆網絡卡的環境下

傳輸1MB資料需要:  1000ms/100MB*1MB+TTL=10ms+TTL,同機房傳輸1MB耗時10毫秒,跨機房理論耗時14毫秒

傳輸4MB資料需要:  1000ms/100MB*4MB+TTL=40ms+TTL,同機房傳輸4MB需要耗時40毫秒,跨機房理論耗時44毫秒

在我的生產環境,同機房的兩個機器之間ping耗時0.15毫秒;兩個機器之間讀1MB資料和4MB的資料延時極度不穩定,在10毫秒~300毫秒之間波動。

另外一個跨機房使用了專線的環境,兩臺機器之間ping耗時4毫秒,但兩個機器之間讀1MB資料和4MB的資料延時也極度不穩定,在40毫秒~500毫秒之間波動。

這個現象看起來就像:網絡卡壓力小時效能差,網絡卡壓力大時效能反而好。

一開始懷疑是網絡卡驅動有問題,

通過修改網絡卡驅動引數,關閉NAPI功能,同機房的傳輸延時有所提升,具體的操作:DisableNAPI功能 ,即更改 ethtool  -C ethx  rx-usecs 0 ,但這個方案有缺點:使得cpu中斷請求變多。

另外一個方案:修改tcp的初始化擁塞視窗,強制將初始化擁塞視窗設定為3,即: ip route | while read p; do ip route change $p initcwnd 3;done  

這兩種方案可以將同機房的讀延時至於理論計算水平。

但這兩種方案,都無法解決跨機房的長延時問題。進一步追蹤如下:

我們測試的延時高,是因為沒有享受Tcp高速通道階段甚至一直處於Tcp慢啟動階段

我做了下面5步嘗試,具體過程如下:

STEP1最開始的測試程式碼:

每次請求建立一個Tcp連線,讀完4MB資料後關閉連線,測試的結果:平均延時174毫秒:每次都新建連線,都要經歷慢啟動階段甚至還沒享受高速階段就結束了,所以延時高。

STEP2改進後的測試程式碼:

只建立一個Tcp連線,Client每隔10秒鐘從Server4MB資料,測試結果:平均延時102毫秒。

改進後延時還非常高,經過觀察擁塞視窗發現每次讀的時候擁塞視窗被重置,從一個較小值增加,tcp又從慢啟動階段開始了。

STEP3改進後的測試程式碼+設定net.ipv4.tcp_slow_start_after_idle=0

只建立一個Tcp連線,Client每隔10秒鐘從Server4MB資料,測試結果:平均延時43毫秒。

net.ipv4.tcp_slow_start_after_idle設定為0,一個tcp連線在空閒後不進入slow start階段,即每次收發資料都直接使用高速通道,平均延時43毫秒,跟計算的理論時間一致。

STEP4我們線上的業務使用了Sofa-Rpc網路框架,這個網路框架複用了Socket連線,每個EndPoint只打開一個Tcp連線。

我使用Sofa-Rpc寫了一個簡單的測試程式碼,Client每隔10秒鐘Rpc呼叫從Server4MB資料,

即:Sofa-Rpc只建立一個Tcp連線+未設定net.ipv4.tcp_slow_start_after_idle(預設為1,測試結果:延時高,跟理論耗時差距較大:transbuf配置為32KB時,平均延時93毫秒。

STEP5

Sofa-Rpc只建立一個Tcp連線+設定net.ipv4.tcp_slow_start_after_idle0,測試結果: transbuf配置為1KB時,平均延時124毫秒;transbuf配置為32KB時,平均延時61毫秒;transbuf配置為4MB時,平均延時55毫秒

使用Sofa-Rpc網路框架,在預設1KBtransbuf時延時124毫秒,不符合預期;

使用Sofa-Rpc網路框架,配置為32KBtransbuf達到較理想的延時61毫秒。32KBSofa-Rpc官方最新版本推薦的transbuf值一致。

結論:

延時高是由於Tcp傳輸沒享受高速通道階段造成的,

1】需要禁止Tcp空閒後慢啟動:設定net.ipv4.tcp_slow_start_after_idle = 0

2】儘量複用Tcp socket連線,保持一直處於高速通道階段

3】我們使用的Sofa-Rpc網路框架,需要把Transbuf設定為32KB以上

另附linux-2.6.32.71核心對tcp idle的定義:

從核心程式碼153行可見在idle時間icsk_rto後需要執行tcp_cwnd_restart()進入慢啟動階段,

Icsk_rto賦值為TCP_TIMEOUT_INIT,其定義為

#define TCP_TIMEOUT_INIT ((unsigned)(3*HZ)) /* RFC 1122 initial RTO value   */