系統性能優化-6 TCP 三次握手
TCP 三次握手
客戶端優化
客戶端發送 SYN 給服務器 此時客戶端連接狀態:SYN_SENT
如果服務器繁忙或中間網絡不暢,客戶端會重發 SYN,重試的次數由 tcp_syn_retries 參數控制,默認是 6 次,第 1 次重試發生在 1 秒鐘后,接著會以翻倍的方式在第 2、4、8、16、32 秒共做 6 次重試,最后一次重試會等待 64 秒,如果仍然沒有返回 ACK,才會終止三次握手。所以,總耗時是 1+2+4+8+16+32+64=127 秒,超過 2 分鐘。
因此,對于內網中通訊時,就可以適當調低重試次數,盡快把錯誤暴露給應用程序。
sysctl net.ipv4.tcp_syn_retries
服務端優化
服務器發送 SYN+ACK 給客戶端 此時服務端連接狀態:SYN_RECV
客戶端收到 SYN+ACK 發送 ACK+數據 到服務端 此時客戶端連接狀態:Established
服務器收到 SYN 報文后,自動回復 SYN + ACK,并把連接放入半連接隊列,當半連接隊列已滿,就無法再建立新連接了
# 查詢服務器因 SYN 半連接隊列滿而丟棄的新連接,是一個【累計值】
netstat -s | grep "SYNs to LISTEN"# 當發現這個值在一直增加時,可以適當增大半連接隊列長度
## 查看當前最大半連接隊伍長度
sysctl net.ipv4.tcp_max_syn_backlog
## 通過這個文件修改后 sysctl -p 即可生效
vim /etc/sysctl.conf
net.ipv4.tcp_max_syn_backlog = 1024
不過開啟 syncookies 功能就可以在不使用 SYN 隊列的情況下成功建立連接
。修改 tcp_syncookies 參數即可,其中值為 0 時表示關閉該功能,2 表示無條件開啟功能,而 1 則表示僅當 SYN 半連接隊列放不下時,再啟用它。由于 syncookie 僅用于應對 SYN 泛洪攻擊(攻擊者惡意構造大量的 SYN 報文發送給服務器,造成 SYN 半連接隊列溢出,導致正常客戶端的連接無法建立),這種方式建立的連接,許多 TCP 特性都無法使用
。所以,應當把 tcp_syncookies 設置為 1,僅在隊列滿時再啟用。
# 開啟 syncookies
vim /etc/sysctl.conf
net.ipv4.tcp_syncookies = 1
sysctl -p
如果服務器發送 syn 和 ack 后一直沒有回應,就會重發,修改重發次數的方法是,調整 tcp_synack_retries 參數:
# 查看 syn ack 的重發次數
sysctl net.ipv4.tcp_synack_retries# 默認值, 1,2,4,8,32 最多等待 63 秒
net.ipv4.tcp_synack_retries = 5
服務端收到 ack + 數據,將連接從半連接隊列移入已連接隊列 服務端連接狀態進入Established
服務器收到 ack 后,就會把連接從 SYN 半連接隊列 移入到 accept 隊列,等待進程調用 accept 函數時把連接取出來。如果進程一直不取,accept 隊列就會溢出,最終導致建立好的 TCP 連接被丟棄。可以選擇向客戶端發送 RST 復位報文,告訴客戶端連接已經建立失敗。
# 默認為 0 不開啟,這樣更有利于應對突發流量,因為此時客戶端的狀態是已連接,就會開始發送報文,只要服務器不回復 ack,就會重發,當服務器不再繁忙,再次接收到的請求報文由于含有 ACK,仍然會觸發服務器端成功建立連接。
tcp_abort_on_overflow = 0
# listen 函數的 backlog 參數就可以設置 accept 隊列的大小,但是同時受到 linux 系統閾值的限制
# Linux 系統級的隊列長度上限
net.core.somaxconn = 128
# 查看各監聽端口上的 accept 隊列長度
ss -ltn# 查看有多少個連接因為隊列溢出而被丟棄
netstat -s | grep "listen queue"
如果持續不斷地有連接因為 accept 隊列溢出被丟棄,就應該調大 backlog 以及 somaxconn 參數。
調查顯示:三次握手消耗的時間,在 HTTP 請求完成的時間占比在 10% 到 30% 之間。因此,Google 提出了 TCP fast open 方案(簡稱TFO),客戶端可以在首個 SYN 報文中就攜帶請求,這節省了 1 個 RTT 的時間
。
TFO 通訊分為兩個階段,第一階段為首次建立連接,這時走正常的三次握手,但在客戶端的 SYN 報文會明確地告訴服務器它想使用 TFO 功能,這樣服務器會把客戶端 IP 地址用只有自己知道的密鑰加密(比如 AES 加密算法)
,作為 Cookie 攜帶在返回的 SYN+ACK 報文中,客戶端收到后會將 Cookie 緩存在本地。
之后,如果客戶端再次向服務器建立連接,就可以在第一個 SYN 報文中攜帶請求數據,同時還要附帶緩存的 Cookie。很顯然,這種通訊方式下不能再采用經典的“先 connect 再 write 請求”這種編程方法,而要改用 sendto 或者 sendmsg 函數才能實現。
服務器收到后,會用自己的密鑰驗證 Cookie 是否合法,驗證通過后連接才算建立成功,再把請求交給進程處理,同時給客戶端返回 SYN+ACK。雖然客戶端收到后還會返回 ACK,但服務器不等收到 ACK 就可以發送 HTTP 響應了,這就減少了握手帶來的 1 個 RTT 的時間消耗。
# 該功能需要客戶端和服務端同時支持,第 1 個比特位為 1 時,表示作為客戶端時支持 TFO;第 2 個比特位為 1 時,表示作為服務器時支持 TFO,因此該值為3時(0x11),表示完全支持 TFO
sysctl net.ipv4.tcp_fastopen
為了確保安全及用戶 IP 會變(如 DHCP ),Cookie 值會隔一段時間變化一次。
[TCP Fast Open --TFO ](https://www.cnblogs.com/codestack/p/18112952)
總結一下文中出現的配置項:
- net.ipv4.tcp_syn_retries 客戶端等待服務端回復 syn+ack 時的最大重試次數,默認為6,分別在 1 + 2 + 4 + 8 + 16 + 32 秒時重試,最后一次重試等待 64 秒,總耗時 127 秒
- net.ipv4.tcp_max_syn_backlog 服務端最大的 SYN 半連接隊列長度,默認 1024,如果設置的過小,會導致無法建立新連接,
netstat -s | grep "SYNs to LISTEN"
指令可以獲得由于半連接隊列已滿而引發的 SYN 丟棄個數 - net.ipv4.tcp_syncookies 服務端在半連接隊列滿的情況下依然可以建立連接,0 表示關閉該功能,2 表示無條件開啟功能,而 1 則表示僅當 SYN 半連接隊列放不下時,再啟用。
這種方式建立的連接,許多 TCP 特性都無法使用,應當設置為 1
。 - net.ipv4.tcp_synack_retries 服務端等待客戶端回復針對 syn+ack 的 ack 的重試次數,默認為 5,最后一次重試后等待 32 秒,總耗時 63 秒。
- net.ipv4.tcp_abort_on_overflow 當 accept 全連接隊列已滿,是否立即中止連接向客戶端發送 RST 復位報文,默認為 0 不開啟,可以更有利于應對突發流量(該連接會暫存在半連接隊列,等客戶端重發 ack 后續還是可以再建立連接),只有非常肯定 accept 隊列會長期溢出時,才能設置為 1 以盡快通知客戶端。
- net.core.somaxconn Linux 系統級的隊列長度上限,與 listen 函數的 backlog 參數配合可以調整 accept 隊列的長度
- net.ipv4.tcp_fastopen TFO 技術,第 1 個 bit 位表示作為客戶端是否支持,第 2 個 bit 位表示作為服務端是否支持。