02.28 高流量大併發Linux TCP性能調優

其實主要是手裡面的跑openvpn服務器。因為並沒有明文禁p2p(哎……想想那麼多流量好像不跑點p2p也跑不完),所以造成有的時候如果有比較多人跑BT的話,會造成VPN速度急劇下降。

本文所面對的情況為:

  高併發數

  高延遲高丟包(典型的美國服務器)

  值得注意的是,因為openvz的VPS權限比較低,能夠修改的地方比較少,所以使用openvz的VPS作VPN服務器是非常不推薦的。

  我們通過修改 /etc/sysctl.conf 來達到調整的目的,注意修改完以後記得使用:

  sysctl -p

  來使修改生效。

  首先,針對高併發數,我們需要提高一些linux的默認限制:

  fs.file-max = 51200

  #提高整個系統的文件限制

  net.ipv4.tcp_syncookies = 1

  #表示開啟SYN Cookies。當出現SYN等待隊列溢出時,啟用cookies來處理,可防範少量SYN攻擊,默認為0,表示關閉;

  net.ipv4.tcp_tw_reuse = 1

  #表示開啟重用。允許將TIME-WAIT sockets重新用於新的TCP連接,默認為0,表示關閉;

  net.ipv4.tcp_tw_recycle = 0

  #表示開啟TCP連接中TIME-WAIT sockets的快速回收,默認為0,表示關閉;

  #為了對NAT設備更友好,建議設置為0。

  net.ipv4.tcp_fin_timeout = 30

  #修改系統默認的 TIMEOUT 時間。

  net.ipv4.tcp_keepalive_time = 1200

  #表示當keepalive起用的時候,TCP發送keepalive消息的頻度。缺省是2小時,改為20分鐘。

  net.ipv4.ip_local_port_range = 10000 65000 #表示用於向外連接的端口範圍。缺省情況下很小:32768到61000,改為10000到65000。(注意:這裡不要將最低值設的太低,否則可能會佔用掉正常的端口!)

  net.ipv4.tcp_max_syn_backlog = 8192

  #表示SYN隊列的長度,默認為1024,加大隊列長度為8192,可以容納更多等待連接的網絡連接數。

  net.ipv4.tcp_max_tw_buckets = 5000

  #表示系統同時保持TIME_WAIT的最大數量,如果超過這個數字,TIME_WAIT將立刻被清除並打印警告信息。

  #額外的,對於內核版本新於**3.7.1**的,我們可以開啟tcp_fastopen:

  net.ipv4.tcp_fastopen = 3

  其次,針對大流量高丟包高延遲的情況,我們通過增大緩存來提高 TCP 性能,自己看E文註釋吧……感覺我翻譯出來各種味道不對 = =:


高流量大併發Linux TCP性能調優

這裡面涉及到一個 TCP 擁塞算法的問題,你可以用下面的命令查看本機提供的擁塞算法控制模塊:

  sysctl net.ipv4.tcp_available_congestion_control

  如果沒有下文提到的htcp,hybla算法,你可以嘗試通過modprobe啟用模塊:

  /sbin/modprobe tcp_htcp

  /sbin/modprobe tcp_hybla

  對於幾種算法的分析,詳情可以參考下: TCP擁塞控制算法 優缺點 適用環境 性能分析 ,但是這裡面沒有涉及到專門為衛星通訊設計的擁塞控制算法:Hybla。根據各位大神的實驗,我們發現Hybla算法恰好是最適合美國服務器的 TCP 擁塞算法,而對於日本服務器,個人想當然的認為htcp算法應該可以比默認的cubic算法達到更好的效果。但是因為htcp算法恰好沒有編入我們所使用的VPS中,所以沒辦法測試

  #設置 TCP 擁塞算法為 hybla

  net.ipv4.tcp_congestion_control=hybla

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 */

linux內核Tcp性能調優

1. fs.file-max

最大可以打開的文件描述符數量,注意是整個系統。

在服務器中,我們知道每創建一個連接,系統就會打開一個文件描述符,所以,文件描述符打開的最大數量也決定了我們的最大連接數

select在高併發情況下被取代的原因也是文件描述符打開的最大值,雖然它可以修改但一般不建議這麼做,詳情可見unp select部分。

2.net.ipv4.tcp_max_syn_backlog

Tcp syn隊列的最大長度,在進行系統調用connect時會發生Tcp的三次握手,server內核會為Tcp維護兩個隊列,Syn隊列和Accept隊列,Syn隊列是指存放完成第一次握手的連接,Accept隊列是存放完成整個Tcp三次握手的連接,修改net.ipv4.tcp_max_syn_backlog使之增大可以接受更多的網絡連接。

注意此參數過大可能遭遇到Syn flood攻擊,即對方發送多個Syn報文端填充滿Syn隊列,使server無法繼續接受其他連接

可參考此文http://tech.uc.cn/?p=1790

我們看下 man 手冊上是如何說的:

The behavior of the backlog argument on TCP sockets changed with Linux 2.2. Now it specifies the queue length for com‐ pletely established sockets waiting to be accepted, instead of the number of incomplete connection requests. The maximum length of the queue for incomplete sockets can be set using /proc/sys/net/ipv4/tcp_max_syn_backlog. When syncookies are enabled there is no logical maximum length and this setting is ignored. See tcp(7) for more information. If the backlog argument is greater than the value in /proc/sys/net/core/somaxconn, then it is silently truncated to that value; the default value in this file is 128. In kernels before 2.4.25, this limit was a hard coded value, SOMAXCONN, with the value 128.

自 Linux 內核 2.2 版本以後,backlog 為已完成連接隊列的最大值,未完成連接隊列大小以 /proc/sys/net/ipv4/tcp_max_syn_backlog 確定,但是已連接隊列大小受 SOMAXCONN 限制,為 min(backlog, SOMAXCONN)

3.net.ipv4.tcp_syncookies

修改此參數可以有效的防範上面所說的syn flood攻擊

原理:在Tcp服務器收到Tcp Syn包並返回Tcp Syn+ack包時,不專門分配一個數據區,而是根據這個Syn包計算出一個cookie值。在收到Tcp ack包時,Tcp服務器在根據那個cookie值檢查這個Tcp ack包的合法性。如果合法,再分配專門的數據區進行處理未來的TCP連接。

默認為0,1表示開啟

4.net.ipv4.tcp_keepalive_time

Tcp keepalive心跳包機制,用於檢測連接是否已斷開,我們可以修改默認時間來間斷心跳包發送的頻率。

keepalive一般是服務器對客戶端進行發送查看客戶端是否在線,因為服務器為客戶端分配一定的資源,但是Tcp 的keepalive機制很有爭議,因為它們可耗費一定的帶寬。

Tcp keepalive詳情見Tcp/ip詳解卷1 第23章

5.net.ipv4.tcp_tw_reuse

我的上一篇文章中寫到了time_wait狀態,大量處於time_wait狀態是很浪費資源的,它們佔用server的描述符等。

修改此參數,允許重用處於time_wait的socket。

默認為0,1表示開啟

6.net.ipv4.tcp_tw_recycle

也是針對time_wait狀態的,該參數表示快速回收處於time_wait的socket。

默認為0,1表示開啟

7.net.ipv4.tcp_fin_timeout

修改time_wait狀的存在時間,默認的2MSL

注意:time_wait存在且生存時間為2MSL是有原因的,見我上一篇博客為什麼會有time_wait狀態的存在,所以修改它有一定的風險,還是根據具體的情況來分析。

8.net.ipv4.tcp_max_tw_buckets

所允許存在time_wait狀態的最大數值,超過則立刻被清楚並且警告。

9.net.ipv4.ip_local_port_range

表示對外連接的端口範圍。

10.somaxconn

前面說了Syn隊列的最大長度限制,somaxconn參數決定Accept隊列長度,在listen函數調用時backlog參數即決定Accept隊列的長度,該參數太小也會限制最大併發連接數,因為同一時間完成3次握手的連接數量太小,server處理連接速度也就越慢。服務器端調用accept函數實際上就是從已連接Accept隊列中取走完成三次握手的連接。

Accept隊列和Syn隊列是listen函數完成創建維護的。

/proc/sys/net/core/somaxconn修改

上面每一個參數其實都夠寫一篇文章來分析了,這裡我只是概述下部分參數,注意在修改Tcp參數時我們一定要根據自己的實際需求以及測試結果來決定。

問題描述

場景:JAVA的client和server,使用socket通信。server使用NIO。

1.間歇性得出現client向server建立連接三次握手已經完成,但server的selector沒有響應到這連接。

2.出問題的時間點,會同時有很多連接出現這個問題。

3.selector沒有銷燬重建,一直用的都是一個。

4.程序剛啟動的時候必會出現一些,之後會間歇性出現。

分析問題

正常TCP建連接三次握手過程:

第一步:client 發送 syn 到server 發起握手;

第二步:server 收到 syn後回覆syn+ack給client;

第三步:client 收到syn+ack後,回覆server一個ack表示收到了server的syn+ack(此時client的56911端口的連接已經是established)。

從問題的描述來看,有點像TCP建連接的時候全連接隊列(accept隊列,後面具體講)滿了,尤其是症狀2、4. 為了證明是這個原因,馬上通過 netstat -s | egrep "listen" 去看隊列的溢出統計數據:

反覆看了幾次之後發現這個overflowed 一直在增加,那麼可以明確的是server上全連接隊列一定溢出了。

接著查看溢出後,OS怎麼處理:

tcp_abort_on_overflow 為0表示如果三次握手第三步的時候全連接隊列滿了那麼server扔掉client 發過來的ack(在server端認為連接還沒建立起來)

為了證明客戶端應用代碼的異常跟全連接隊列滿有關係,我先把tcp_abort_on_overflow修改成 1,1表示第三步的時候如果全連接隊列滿了,server發送一個reset包給client,表示廢掉這個握手過程和這個連接(本來在server端這個連接就還沒建立起來)。

接著測試,這時在客戶端異常中可以看到很多connection reset by peer的錯誤,到此證明客戶端錯誤是這個原因導致的(邏輯嚴謹、快速證明問題的關鍵點所在)。

於是開發同學翻看java 源代碼發現socket 默認的backlog(這個值控制全連接隊列的大小,後面再詳述)是50,於是改大重新跑,經過12個小時以上的壓測,這個錯誤一次都沒出現了,同時觀察到 overflowed 也不再增加了。

到此問題解決,簡單來說TCP三次握手後有個accept隊列,進到這個隊列才能從Listen變成accept,默認backlog 值是50,很容易就滿了。滿了之後握手第三步的時候server就忽略了client發過來的ack包(隔一段時間server重發握手第二步的syn+ack包給client),如果這個連接一直排不上隊就異常了。

但是不能只是滿足問題的解決,而是要去覆盤解決過程,中間涉及到了哪些知識點是我所缺失或者理解不到位的;這個問題除了上面的異常信息表現出來之外,還有沒有更明確地指徵來查看和確認這個問題。

深入理解TCP握手過程中建連接的流程和隊列


高流量大併發Linux TCP性能調優

如上圖所示,這裡有兩個隊列:syns queue(半連接隊列);accept queue(全連接隊列)。

三次握手中,在第一步server收到client的syn後,把這個連接信息放到半連接隊列中,同時回覆syn+ack給client(第二步);

第三步的時候server收到client的ack,如果這時全連接隊列沒滿,那麼從半連接隊列拿出這個連接的信息放入到全連接隊列中,否則按tcp_abort_on_overflow指示的執行。

這時如果全連接隊列滿了並且tcp_abort_on_overflow是0的話,server過一段時間再次發送syn+ack給client(也就是重新走握手的第二步),如果client超時等待比較短,client就很容易異常了。

在我們的os中retry 第二步的默認次數是2(centos默認是5次)


高流量大併發Linux TCP性能調優

如果TCP連接隊列溢出,有哪些指標可以看呢?

上述解決過程有點繞,聽起來懵,那麼下次再出現類似問題有什麼更快更明確的手段來確認這個問題呢?(通過具體的、感性的東西來強化我們對知識點的理解和吸收。)

netstat -s


高流量大併發Linux TCP性能調優

比如上面看到的 667399 times ,表示全連接隊列溢出的次數,隔幾秒鐘執行下,如果這個數字一直在增加的話肯定全連接隊列偶爾滿了。

ss 命令


高流量大併發Linux TCP性能調優

上面看到的第二列Send-Q 值是50,表示第三列的listen端口上的全連接隊列最大為50,第一列Recv-Q為全連接隊列當前使用了多少。

全連接隊列的大小取決於:min(backlog, somaxconn) . backlog是在socket創建的時候傳入的,somaxconn是一個os級別的系統參數。

這個時候可以跟我們的代碼建立聯繫了,比如Java創建ServerSocket的時候會讓你傳入backlog的值:


關注+後臺私信;資料;兩個字可以免費領取 資料內容包括:C/C++,Linux,golang,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK,嵌入式 等。。。


分享到:


相關文章: