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功能,同機房的傳輸延時有所提升,具體的操作:Disable掉NAPI功能 ,即更改 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秒鐘從Server讀4MB資料,測試結果:平均延時102毫秒。
改進後延時還非常高,經過觀察擁塞視窗發現每次讀的時候擁塞視窗被重置,從一個較小值增加,tcp又從慢啟動階段開始了。
STEP3】改進後的測試程式碼+設定net.ipv4.tcp_slow_start_after_idle=0:
只建立一個Tcp連線,Client每隔10秒鐘從Server讀4MB資料,測試結果:平均延時43毫秒。
net.ipv4.tcp_slow_start_after_idle設定為0,一個tcp連線在空閒後不進入slow start階段,即每次收發資料都直接使用高速通道,平均延時43毫秒,跟計算的理論時間一致。
STEP4】我們線上的業務使用了Sofa-Rpc網路框架,這個網路框架複用了Socket連線,每個EndPoint只打開一個Tcp連線。
我使用Sofa-Rpc寫了一個簡單的測試程式碼,Client每隔10秒鐘Rpc呼叫從Server讀4MB資料,
即:Sofa-Rpc只建立一個Tcp連線+未設定net.ipv4.tcp_slow_start_after_idle(預設為1),測試結果:延時高,跟理論耗時差距較大:transbuf配置為32KB時,平均延時93毫秒。
STEP5】
Sofa-Rpc只建立一個Tcp連線+設定net.ipv4.tcp_slow_start_after_idle為0,測試結果: transbuf配置為1KB時,平均延時124毫秒;transbuf配置為32KB時,平均延時61毫秒;transbuf配置為4MB時,平均延時55毫秒
使用Sofa-Rpc網路框架,在預設1KB的transbuf時延時124毫秒,不符合預期;
使用Sofa-Rpc網路框架,配置為32KB的transbuf達到較理想的延時61毫秒。32KB跟Sofa-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 */