背景
我們在日常開發中經常會使用到TCP相關技術,但我們往往不會去關心TCP連接本身,下面會給大家介紹一下TCP的一些優化點。TCP 協議是由操作系統實現,所以操作系統提供了不少調節 TCP 的參數。如何正確有效的使用這些參數,我們可以從TCP三次握手來提升TCP的性能。
TCP三次握手的性能提升
我們知道TCP 是面向連接的、可靠的、雙向傳輸的傳輸層通信協議,在傳輸數據之前需要經過三次握手才能建立連接。
三次握手的過程在一個 HTTP 請求的平均時間占比 10% 以上,在網絡狀態不佳、高并發或者遭遇 SYN 攻擊等場景中,如果不能有效正確的調節三次握手中的參數,就會對性能產生很多的影響。如何正確有效的使用這些參數,來提高 TCP 三次握手的性能,這就需要理解”三次握手的狀態變遷”,這樣當出現問題時,先用 netstat命令查看是哪個握手階段出現了問題
客戶端和服務端都可以針對三次握手優化性能,但優化方式不同,接下來分別針對客戶端和服務端介紹一下如何進行優化
客戶端優化
三次握手建立連接的首要目的是「同步序列號」。只有同步了序列號才有可靠傳輸,TCP 許多特性都依賴于序列號實現,比如流量控制、丟包重傳等,這也是三次握手中的報文稱為 SYN 的原因。
客戶端作為主動發起連接方,首先它將發送 SYN 包,于是客戶端的連接就會處于 SYN_SENT 狀態。客戶端在等待服務端回復的 ACK 報文,正常情況下,服務器會在幾毫秒內返回 SYN+ACK ,但如果客戶端長時間沒有收到 SYN+ACK 報文,則會重發 SYN 包,重發的次數由 TCP_syn_retries 參數控制,默認是 5 次。
通常,第一次超時重傳是在 1 秒后,第二次超時重傳是在 2 秒,第三次超時重傳是在 4 秒后,第四次超時重傳是在 8 秒后,第五次是在超時重傳 16 秒后。沒錯,每次超時的時間是上一次的 2 倍。當第五次超時重傳后,會繼續等待 32 秒,如果服務端仍然沒有回應 ACK,客戶端就會終止三次握手。所以,總耗時是 1+2+4+8+16+32=63 秒,大約 1 分鐘左右。
可以根據網絡的穩定性和目標服務器的繁忙程度修改 SYN 的重傳次數,調整客戶端的三次握手時間上限。比如內網中通訊時,就可以適當調低重試次數,盡快把錯誤暴露給應用程序。
服務端優化
服務端收到 SYN 包后,服務端會立馬回復 SYN+ACK 包,表明確認收到了客戶端的序列號,同時也把自己的序列號發給對方。此時,服務端出現了新連接,狀態是 SYN_RCV。在這個狀態下,Linux 內核就會建立一個「半連接隊列」來維護「未完成」的握手信息,當半連接隊列溢出后,服務端就無法再建立新的連接。
我們可以通過該 netstat -s 命令給出的統計結果中, 可以得到由于半連接隊列已滿,引發的失敗次數,若失敗次數一直增加,那我們需要增加半連接對了。要想增大半連接隊列,不能只單純增大tcp_max_syn_backlog 的值,還需一同增大 somaxconn 和 backlog,也就是增大 accept 隊列。否則,只單純增大 tcp_max_syn_backlog 是無效的。增大 tcp_max_syn_backlog 和 somaxconn 的方法是修改 Linux 內核參數:
增大 backlog 的方式,每個 Web 服務都不同,比如 Nginx 增大 backlog 的方法如下:
改變了如上這些參數后,要重啟 Nginx 服務,因為 SYN 半連接隊列和 accept 隊列都是在 listen() 初始化的。
SYN_RCV狀態的優化
客戶端接收到服務器發來的 SYN+ACK 報文后,就會回復 ACK 給服務器,同時客戶端連接狀態從 SYN_SENT 轉換為 ESTABLISHED,表示連接建立成功。服務器端連接成功建立的時間還要再往后,等到服務端收到客戶端的 ACK 后,服務端的連接狀態才變為 ESTABLISHED。如果服務器沒有收到 ACK,就會重發 SYN+ACK 報文,同時一直處于 SYN_RCV 狀態。
當網絡繁忙、不穩定時,報文丟失就會變嚴重,此時應該調大重發次數。反之則可以調小重發次數。修改重發次數的方法是,調整 tcp_synack_retries 參數:
tcp_synack_retries 的默認重試次數是 5 次,與客戶端重傳 SYN 類似,它的重傳會經歷 1、2、4、8、16 秒,最后一次重傳后會繼續等待 32 秒,如果服務端仍然沒有收到 ACK,才會關閉連接,故共需要等待 63 秒。服務器收到 ACK 后連接建立成功,此時,內核會把連接從半連接隊列移除,然后創建新的完全的連接,并將其添加到 accept 隊列,等待進程調用 accept 函數時把連接取出來。
Accept隊列調整
丟棄連接只是 Linux 的默認行為,我們還可以選擇向客戶端發送 RST 復位報文,告訴客戶端連接已經建立失敗。打開這一功能需要將 tcp_abort_on_overflow 參數設置為 1。
tcp_abort_on_overflow 共有兩個值分別是 0 和 1,其分別表示:
0 :如果 accept 隊列滿了,那么 server 扔掉 client 發過來的 ack ;
1 :如果 accept 隊列滿了,server 發送一個 RST 包給 client,表示廢掉這個握手過程和這個連接;
如果要想知道客戶端連接不上服務端,是不是服務端 TCP 全連接隊列滿的原因,那么可以把tcp_abort_on_overflow 設置為 1,這時如果在客戶端異常中可以看到很多 connection reset by peer 的錯誤,那么就可以證明是由于服務端 TCP 全連接隊列溢出的問題。通常情況下,應當把 tcp_abort_on_overflow 設置為 0,因為這樣更有利于應對突發流量。
所以,tcp_abort_on_overflow 設為 0 可以提高連接建立的成功率,只有你非常肯定 TCP 全連接隊列會長期溢出時,才能設置為 1 以盡快通知客戶端。
總結
TCP優化三次握手可以從四個角度入手出發進行調整,調整SYN報文的重傳次數、調整SYN半連接隊列長度、調整SYN+ACK報文的重傳次數、調整accpet隊列長度。