其實主要是手里面的跑openvpn服務器。因為并沒有明文禁p2p(哎……想想那么多流量好像不跑點p2p也跑不完),所以造成有的時候如果有比較多人跑BT的話,會造成VPN速度急劇下降。
本文所面對的情況為:
高并發(fā)數(shù)
高延遲高丟包(典型的美國服務器)
值得注意的是,因為openvz的VPS權限比較低,能夠修改的地方比較少,所以使用openvz的VPS作VPN服務器是非常不推薦的。
我們通過修改 /etc/sysctl.conf 來達到調整的目的,注意修改完以后記得使用:
sysctl -p
來使修改生效。
首先,針對高并發(fā)數(shù),我們需要提高一些linux的默認限制:
fs.file-max = 51200
#提高整個系統(tǒng)的文件限制
net.ipv4.tcp_syncookies = 1
#表示開啟SYN Cookies。當出現(xiàn)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
#修改系統(tǒng)默認的 TIMEOUT 時間。
net.ipv4.tcp_keepalive_time = 1200
#表示當keepalive起用的時候,TCP發(fā)送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,可以容納更多等待連接的網絡連接數(shù)。
net.ipv4.tcp_max_tw_buckets = 5000
#表示系統(tǒng)同時保持TIME_WAIT的最大數(shù)量,如果超過這個數(shù)字,TIME_WAIT將立刻被清除并打印警告信息。
#額外的,對于內核版本新于**3.7.1**的,我們可以開啟tcp_fastopen:
net.ipv4.tcp_fastopen = 3
其次,針對大流量高丟包高延遲的情況,我們通過增大緩存來提高 TCP 性能,自己看E文注釋吧……感覺我翻譯出來各種味道不對 = =:
這里面涉及到一個 TCP 擁塞算法的問題,你可以用下面的命令查看本機提供的擁塞算法控制模塊:
sysctl net.ipv4.tcp_available_congestion_control
如果沒有下文提到的htcp,hybla算法,你可以嘗試通過modprobe啟用模塊:
/sbin/modprobe tcp_htcp
/sbin/modprobe tcp_hybla
對于幾種算法的分析,詳情可以參考下: TCP擁塞控制算法 優(yōu)缺點 適用環(huán)境 性能分析 ,但是這里面沒有涉及到專門為衛(wèi)星通訊設計的擁塞控制算法:Hybla。根據各位大神的實驗,我們發(fā)現(xiàn)Hybla算法恰好是最適合美國服務器的 TCP 擁塞算法,而對于日本服務器,個人想當然的認為htcp算法應該可以比默認的cubic算法達到更好的效果。但是因為htcp算法恰好沒有編入我們所使用的VPS中,所以沒辦法測試。
#設置 TCP 擁塞算法為 hybla
net.ipv4.tcp_congestion_control=hybla
Tcp性能調優(yōu) 解決Tcp長延時
根據Tcp的理論計算,Tcp最佳狀態(tài)下傳輸是流水并行的,傳輸時間等于傳輸數(shù)據耗時+TTL,即千兆網卡的環(huán)境下
傳輸1MB數(shù)據需要: 1000ms/100MB*1MB+TTL=10ms+TTL,同機房傳輸1MB耗時10毫秒,跨機房理論耗時14毫秒
傳輸4MB數(shù)據需要: 1000ms/100MB*4MB+TTL=40ms+TTL,同機房傳輸4MB需要耗時40毫秒,跨機房理論耗時44毫秒
在我的生產環(huán)境,同機房的兩個機器之間ping耗時0.15毫秒;兩個機器之間讀1MB數(shù)據和4MB的數(shù)據延時極度不穩(wěn)定,在10毫秒~300毫秒之間波動。
另外一個跨機房使用了專線的環(huán)境,兩臺機器之間ping耗時4毫秒,但兩個機器之間讀1MB數(shù)據和4MB的數(shù)據延時也極度不穩(wěn)定,在40毫秒~500毫秒之間波動。
這個現(xiàn)象看起來就像:網卡壓力小時性能差,網卡壓力大時性能反而好。
一開始懷疑是網卡驅動有問題,
通過修改網卡驅動參數(shù),關閉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數(shù)據后關閉連接,測試的結果:平均延時174毫秒:每次都新建連接,都要經歷慢啟動階段甚至還沒享受高速階段就結束了,所以延時高。
STEP2】 改進后的測試代碼:
只建立一個Tcp連接,Client每隔10秒鐘從Server讀4MB數(shù)據,測試結果:平均延時102毫秒。
改進后延時還非常高,經過觀察擁塞窗口發(fā)現(xiàn)每次讀的時候擁塞窗口被重置,從一個較小值增加,tcp又從慢啟動階段開始了。
STEP3】改進后的測試代碼+設置net.ipv4.tcp_slow_start_after_idle=0:
只建立一個Tcp連接,Client每隔10秒鐘從Server讀4MB數(shù)據,測試結果:平均延時43毫秒。
net.ipv4.tcp_slow_start_after_idle設置為0,一個tcp連接在空閑后不進入slow start階段,即每次收發(fā)數(shù)據都直接使用高速通道,平均延時43毫秒,跟計算的理論時間一致。
STEP4】我們線上的業(yè)務使用了Sofa-Rpc網絡框架,這個網絡框架復用了Socket連接,每個EndPoint只打開一個Tcp連接。
我使用Sofa-Rpc寫了一個簡單的測試代碼,Client每隔10秒鐘Rpc調用從Server讀4MB數(shù)據,
即: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后需要執(zhí)行tcp_cwnd_restart()進入慢啟動階段,
Icsk_rto賦值為TCP_TIMEOUT_INIT,其定義為
#define TCP_TIMEOUT_INIT ((unsigned)(3*HZ)) /* RFC 1122 initial RTO value */
linux內核Tcp性能調優(yōu)
1. fs.file-max
最大可以打開的文件描述符數(shù)量,注意是整個系統(tǒng)。
在服務器中,我們知道每創(chuàng)建一個連接,系統(tǒng)就會打開一個文件描述符,所以,文件描述符打開的最大數(shù)量也決定了我們的最大連接數(shù)
select在高并發(fā)情況下被取代的原因也是文件描述符打開的最大值,雖然它可以修改但一般不建議這么做,詳情可見unp select部分。
2.net.ipv4.tcp_max_syn_backlog
Tcp syn隊列的最大長度,在進行系統(tǒng)調用connect時會發(fā)生Tcp的三次握手,server內核會為Tcp維護兩個隊列,Syn隊列和Accept隊列,Syn隊列是指存放完成第一次握手的連接,Accept隊列是存放完成整個Tcp三次握手的連接,修改net.ipv4.tcp_max_syn_backlog使之增大可以接受更多的網絡連接。
注意此參數(shù)過大可能遭遇到Syn flood攻擊,即對方發(fā)送多個Syn報文端填充滿Syn隊列,使server無法繼續(xù)接受其他連接
可參考此文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
修改此參數(shù)可以有效的防范上面所說的syn flood攻擊
原理:在Tcp服務器收到Tcp Syn包并返回Tcp Syn+ack包時,不專門分配一個數(shù)據區(qū),而是根據這個Syn包計算出一個cookie值。在收到Tcp ack包時,Tcp服務器在根據那個cookie值檢查這個Tcp ack包的合法性。如果合法,再分配專門的數(shù)據區(qū)進行處理未來的TCP連接。
默認為0,1表示開啟
4.net.ipv4.tcp_keepalive_time
Tcp keepalive心跳包機制,用于檢測連接是否已斷開,我們可以修改默認時間來間斷心跳包發(fā)送的頻率。
keepalive一般是服務器對客戶端進行發(fā)送查看客戶端是否在線,因為服務器為客戶端分配一定的資源,但是Tcp 的keepalive機制很有爭議,因為它們可耗費一定的帶寬。
Tcp keepalive詳情見Tcp/ip詳解卷1 第23章
5.net.ipv4.tcp_tw_reuse
我的上一篇文章中寫到了time_wait狀態(tài),大量處于time_wait狀態(tài)是很浪費資源的,它們占用server的描述符等。
修改此參數(shù),允許重用處于time_wait的socket。
默認為0,1表示開啟
6.net.ipv4.tcp_tw_recycle
也是針對time_wait狀態(tài)的,該參數(shù)表示快速回收處于time_wait的socket。
默認為0,1表示開啟
7.net.ipv4.tcp_fin_timeout
修改time_wait狀的存在時間,默認的2MSL
注意:time_wait存在且生存時間為2MSL是有原因的,見我上一篇博客為什么會有time_wait狀態(tài)的存在,所以修改它有一定的風險,還是根據具體的情況來分析。
8.net.ipv4.tcp_max_tw_buckets
所允許存在time_wait狀態(tài)的最大數(shù)值,超過則立刻被清楚并且警告。
9.net.ipv4.ip_local_port_range
表示對外連接的端口范圍。
10.somaxconn
前面說了Syn隊列的最大長度限制,somaxconn參數(shù)決定Accept隊列長度,在listen函數(shù)調用時backlog參數(shù)即決定Accept隊列的長度,該參數(shù)太小也會限制最大并發(fā)連接數(shù),因為同一時間完成3次握手的連接數(shù)量太小,server處理連接速度也就越慢。服務器端調用accept函數(shù)實際上就是從已連接Accept隊列中取走完成三次握手的連接。
Accept隊列和Syn隊列是listen函數(shù)完成創(chuàng)建維護的。
/proc/sys/net/core/somaxconn修改
上面每一個參數(shù)其實都夠寫一篇文章來分析了,這里我只是概述下部分參數(shù),注意在修改Tcp參數(shù)時我們一定要根據自己的實際需求以及測試結果來決定。
問題描述
場景:JAVA的client和server,使用socket通信。server使用NIO。
1.間歇性得出現(xiàn)client向server建立連接三次握手已經完成,但server的selector沒有響應到這連接。
2.出問題的時間點,會同時有很多連接出現(xiàn)這個問題。
3.selector沒有銷毀重建,一直用的都是一個。
4.程序剛啟動的時候必會出現(xiàn)一些,之后會間歇性出現(xiàn)。
分析問題
正常TCP建連接三次握手過程:
第一步:client 發(fā)送 syn 到server 發(fā)起握手;
第二步:server 收到 syn后回復syn+ack給client;
第三步:client 收到syn+ack后,回復server一個ack表示收到了server的syn+ack(此時client的56911端口的連接已經是established)。
從問題的描述來看,有點像TCP建連接的時候全連接隊列(accept隊列,后面具體講)滿了,尤其是癥狀2、4. 為了證明是這個原因,馬上通過 netstat -s | egrep "listen" 去看隊列的溢出統(tǒng)計數(shù)據:
反復看了幾次之后發(fā)現(xiàn)這個overflowed 一直在增加,那么可以明確的是server上全連接隊列一定溢出了。
接著查看溢出后,OS怎么處理:
tcp_abort_on_overflow 為0表示如果三次握手第三步的時候全連接隊列滿了那么server扔掉client 發(fā)過來的ack(在server端認為連接還沒建立起來)
為了證明客戶端應用代碼的異常跟全連接隊列滿有關系,我先把tcp_abort_on_overflow修改成 1,1表示第三步的時候如果全連接隊列滿了,server發(fā)送一個reset包給client,表示廢掉這個握手過程和這個連接(本來在server端這個連接就還沒建立起來)。
接著測試,這時在客戶端異常中可以看到很多connection reset by peer的錯誤,到此證明客戶端錯誤是這個原因導致的(邏輯嚴謹、快速證明問題的關鍵點所在)。
于是開發(fā)同學翻看java 源代碼發(fā)現(xiàn)socket 默認的backlog(這個值控制全連接隊列的大小,后面再詳述)是50,于是改大重新跑,經過12個小時以上的壓測,這個錯誤一次都沒出現(xiàn)了,同時觀察到 overflowed 也不再增加了。
到此問題解決,簡單來說TCP三次握手后有個accept隊列,進到這個隊列才能從Listen變成accept,默認backlog 值是50,很容易就滿了。滿了之后握手第三步的時候server就忽略了client發(fā)過來的ack包(隔一段時間server重發(fā)握手第二步的syn+ack包給client),如果這個連接一直排不上隊就異常了。
但是不能只是滿足問題的解決,而是要去復盤解決過程,中間涉及到了哪些知識點是我所缺失或者理解不到位的;這個問題除了上面的異常信息表現(xiàn)出來之外,還有沒有更明確地指征來查看和確認這個問題。
深入理解TCP握手過程中建連接的流程和隊列
如上圖所示,這里有兩個隊列:syns queue(半連接隊列);accept queue(全連接隊列)。
三次握手中,在第一步server收到client的syn后,把這個連接信息放到半連接隊列中,同時回復syn+ack給client(第二步);
第三步的時候server收到client的ack,如果這時全連接隊列沒滿,那么從半連接隊列拿出這個連接的信息放入到全連接隊列中,否則按tcp_abort_on_overflow指示的執(zhí)行。
這時如果全連接隊列滿了并且tcp_abort_on_overflow是0的話,server過一段時間再次發(fā)送syn+ack給client(也就是重新走握手的第二步),如果client超時等待比較短,client就很容易異常了。
在我們的os中retry 第二步的默認次數(shù)是2(centos默認是5次)
如果TCP連接隊列溢出,有哪些指標可以看呢?
上述解決過程有點繞,聽起來懵,那么下次再出現(xiàn)類似問題有什么更快更明確的手段來確認這個問題呢?(通過具體的、感性的東西來強化我們對知識點的理解和吸收。)
netstat -s
比如上面看到的 667399 times ,表示全連接隊列溢出的次數(shù),隔幾秒鐘執(zhí)行下,如果這個數(shù)字一直在增加的話肯定全連接隊列偶爾滿了。
ss 命令
上面看到的第二列Send-Q 值是50,表示第三列的listen端口上的全連接隊列最大為50,第一列Recv-Q為全連接隊列當前使用了多少。
全連接隊列的大小取決于:min(backlog, somaxconn) . backlog是在socket創(chuàng)建的時候傳入的,somaxconn是一個os級別的系統(tǒng)參數(shù)。
這個時候可以跟我們的代碼建立聯(lián)系了,比如Java創(chuàng)建ServerSocket的時候會讓你傳入backlog的值: