TCP 的優(yōu)化

整理自 CSDN 公眾號

一、三次握手

1. 客戶端
TCP 三次握手的開始是客戶端發(fā)起 SYN,如果服務(wù)端沒有及時(shí)回復(fù),那么會重傳,重傳的間隔和次數(shù)是可控的,默認(rèn)是五次,第一次間隔 1 秒,第二次 2 秒,第三次 4 秒,第四次 8 秒,第五次16 秒,最終超時(shí)時(shí)間是 63 秒,因此在優(yōu)化時(shí)可以修改重傳次數(shù)和間隔,以盡快把錯(cuò)誤暴露給應(yīng)用程序。

2. 服務(wù)端的半連接隊(duì)列優(yōu)化
服務(wù)端在第一次返回 SYN + ACK 時(shí),就會把這次請求維護(hù)進(jìn)一個(gè)半連接隊(duì)列,這個(gè)隊(duì)列用來維護(hù)尚未完成的握手信息(相對于全連接),如果這個(gè)隊(duì)列溢出了,服務(wù)端就無法繼續(xù)接受新的請求了,這也是 SYN Flood 攻擊的點(diǎn)。
通過一個(gè)命令 netstat -s 可以得到累計(jì)的、由于半連接隊(duì)列已滿引發(fā)的失敗次數(shù),隔幾秒執(zhí)行一次就可以知道這個(gè)次數(shù)是否有上升的趨勢以及分析是否正常。
這種 SYN Flood 攻擊之所以成立,是因?yàn)榫S護(hù)這個(gè)半連接隊(duì)列一定要分配一定的內(nèi)存資源,那么應(yīng)對的方式之一 syncookies 就是如何不分配資源的前提下,可以確認(rèn)是一次有效的連接并 establish。
syncookies 的工作原理是,服務(wù)器使用一種算法,計(jì)算出一個(gè)哈希值,它包含了客戶端發(fā)來請求的部分信息,再將這個(gè)哈希值和 SYN+ACK 一起返回給客戶端,客戶端也經(jīng)過一些運(yùn)算,再返回給服務(wù)端,那么服務(wù)端根據(jù)這個(gè)返回值和之前的計(jì)算值比較,如果合法,就可以建立有效連接,從而不會占據(jù)半連接隊(duì)列的內(nèi)存。應(yīng)對 SYN 攻擊時(shí),只需將 syncookies 的參數(shù)值調(diào)為 1(半連接隊(duì)列溢出時(shí)啟用),即可。
相當(dāng)?shù)?,可以增大半連接隊(duì)列,但是要和 accept 的隊(duì)列同時(shí)增大才有效,(否則會導(dǎo)致 accept 隊(duì)列溢出同樣丟失 TCP 連接)
此時(shí),對于客戶端來說已經(jīng)是 established 狀態(tài),但是還要再返回給服務(wù)端一個(gè) ACK,服務(wù)端收到后,服務(wù)端才是 established 狀態(tài)并開始傳數(shù)據(jù),如果網(wǎng)絡(luò)不穩(wěn)定,同樣的,服務(wù)端會重發(fā) SYN+ACK,當(dāng)網(wǎng)絡(luò)不穩(wěn)定時(shí),應(yīng)該增加服務(wù)端重發(fā) SYN+ACK 的次數(shù)。

3. 服務(wù)端的 accept 隊(duì)列優(yōu)化
當(dāng)連接已經(jīng)建立、應(yīng)用程序尚未調(diào)用時(shí),TCP 連接會被保存在一個(gè) accept 隊(duì)列中,如果進(jìn)程未能及時(shí)調(diào)用,就會導(dǎo)致 accept 隊(duì)列溢出,溢出部分連接將被默認(rèn)丟棄。對此可以做的是,選擇向客戶端發(fā)送 RST 報(bào)文,告知關(guān)閉這個(gè)連接,丟棄握手過程。打開這一功能需要將 tcp_abort_on_overflow 參數(shù)設(shè)置為 1。如果想讓客戶端了解是由于 accept 隊(duì)列溢出造成連接失敗可以這樣做。當(dāng) tcp_abort_on_overflow 參數(shù)設(shè)置為 0 時(shí),則如果 accept 隊(duì)列溢出,就會丟棄客戶端傳來的 ACK(用于最后一次握手)。
應(yīng)對高并發(fā)流量時(shí),更好的選擇是 tcp_abort_on_overflow 參數(shù)設(shè)置為 0,這樣對于客戶端它的狀態(tài)仍然是 established,客戶端會定時(shí)發(fā)送帶有 ack 報(bào)文的發(fā)送數(shù)據(jù)請求,一旦服務(wù)端的 accept 隊(duì)列有空位,那么連接仍有可能建立成功。所以只有很確定在一段時(shí)間內(nèi) accept 都是將溢出的狀態(tài),才推薦 tcp_abort_on_overflow 參數(shù)設(shè)置為 1。
同樣的,可以調(diào)整 accept 隊(duì)列長度,也可以查看累計(jì)的由于溢出導(dǎo)致丟失的連接總數(shù),來判斷趨勢。

二、繞過三次握手

在 Linux 3.7 內(nèi)核版本之后,提供了 TCP Fast Open 功能,這個(gè)功能如此生效:
初次建立 TCP 連接時(shí),客戶端在第一個(gè) SYN 包中傳入一個(gè)請求 cookie,表明打開 fast open 功能,服務(wù)端對應(yīng)生成一個(gè) cookie 給客戶端,除此之外,三次握手沒有不同,但是,在 cookie 沒有過期之前,下一次再連接的時(shí)候,客戶端發(fā)送帶有 cookie 的 SYN 包,服務(wù)端校驗(yàn)了 cookie 有效以后,就可以開始傳輸數(shù)據(jù)了,從而節(jié)約了一個(gè)往返的時(shí)間消耗。
TCP Fast Open 功能需要服務(wù)端和客戶端同時(shí)打開才能生效。

三、四次揮手

(備注一個(gè)之前看到差點(diǎn)忘了的知識點(diǎn)。
當(dāng)主動方收到被動方的 FIN 報(bào)文后,內(nèi)核會回復(fù) ACK 報(bào)文給被動方,同時(shí)主動方的連接狀態(tài)由 FIN_WAIT2 變?yōu)?TIME_WAIT,在 Linux 系統(tǒng)下大約等待 1 分鐘后,TIME_WAIT 狀態(tài)的連接才會徹底關(guān)閉。

  • 之所以還要再等一分鐘,是因?yàn)槿绻詈蠡貜?fù)的 ACK 丟失,那么被動方還會發(fā)送 FIN,如果在這個(gè)時(shí)間窗口沒有等待而是直接關(guān)閉,并且剛好有其他客戶端連接到同一端口,就會被直接關(guān)閉或發(fā)生數(shù)據(jù)錯(cuò)亂的問題)

1. 主動方的優(yōu)化
關(guān)閉的方式有兩種 RST 和 FIN,RST 是暴力關(guān)閉連接的方式,安全關(guān)閉連接則必須四次揮手。
FIN 報(bào)文關(guān)閉則可以使用 close 和 shutdown 兩種函數(shù)來實(shí)現(xiàn)。close 相對來說是“不優(yōu)雅”的,調(diào)用 close 的一方的連接叫做「孤兒連接」,會同時(shí)關(guān)閉讀和寫,而 shutdown 可以控制是讀還是寫。
關(guān)閉讀的時(shí)候,會丟棄接收緩沖區(qū)里的所有數(shù)據(jù),如果后續(xù)再接受到數(shù)據(jù),也會悄悄丟棄,并發(fā)送 ACK,對方不會知道被丟棄了。
關(guān)閉寫的時(shí)候,會把發(fā)送緩沖區(qū)的數(shù)據(jù)全部發(fā)送并發(fā)送 FIN。

(1)FIN_WAIT1 的優(yōu)化
主動方發(fā)送 FIN 以后,進(jìn)入 FIN_WAIT1 狀態(tài),如果遲遲沒收到 ACK,會定時(shí)重發(fā) FIN,重發(fā)次數(shù)由 tcp_orphan_retries 參數(shù)控制,默認(rèn)為 8 次,如果處于 FIN_WAIT1 狀態(tài)的連接過多,應(yīng)該考慮降低次數(shù),重發(fā)次數(shù)超過參數(shù)時(shí),連接會被直接關(guān)閉。
如果遇到惡意攻擊,可能無法發(fā)送出 FIN,因?yàn)?TCP 按順序發(fā)送所有包, FIN 也不能繞過,另外如果對方的接收窗口已經(jīng)滿了,發(fā)送方也無法再發(fā)送數(shù)據(jù)。
此時(shí)應(yīng)該做的是調(diào)整 tcp_max_orphans 參數(shù),它定義了「孤兒連接」的最大數(shù)量,當(dāng)系統(tǒng)中的孤兒連接超過參數(shù)值,新增的孤兒連接不會再處于 FIN_WAIT1 狀態(tài),而是會被 RST 報(bào)文直接關(guān)閉。(只會影響 CLOSE 函數(shù)關(guān)閉的連接,不會影響 shutdown 關(guān)閉的,不會影響還有讀或?qū)懙目赡埽?/p>

(2)FIN_WAIT2 的優(yōu)化
主動方收到 ACK 后,會處于 FIN_WAIT2,因?yàn)楸粍臃竭€可能有數(shù)據(jù)發(fā)送,如果是 shutdown 關(guān)閉,那它也可能還會發(fā)送數(shù)據(jù),但是對于 close 關(guān)閉的連接,無法再發(fā)送和接收數(shù)據(jù),保持在 FIN_WAIT2 的狀態(tài)已經(jīng)沒有太大意義,tcp_fin_timeout 控制了這個(gè)狀態(tài)下連接的持續(xù)時(shí)長,默認(rèn)值是 60 秒。這個(gè)時(shí)間和 TIME_WAIT 狀態(tài)時(shí)長是一致的。

(3)TIME_WAIT 的優(yōu)化
TIME_WAIT 和 FIN_WAIT2 的時(shí)間是一致的,都是 2MSL,1MSL 表示一個(gè)報(bào)文在網(wǎng)絡(luò)中存活的最長時(shí)間(報(bào)文每經(jīng)過一次路由器的轉(zhuǎn)發(fā),IP 頭部的 TTL 字段就會減 1,減到 0 時(shí)報(bào)文就被丟棄,這就限制了報(bào)文的最長存活時(shí)間),那么為什么是等待 2MSL 呢,其實(shí)就是允許報(bào)文至少丟失一次、再發(fā)送一次,這樣第一個(gè)丟失了,等待的時(shí)間里第二個(gè) ACK 還會到達(dá),為什么不是 4MSL 以上呢,這是一個(gè)概率的問題,如果一個(gè)網(wǎng)絡(luò)丟包率達(dá)到 1%,那么連續(xù)兩次丟包的概率是萬分之一,不必為了這種概率增加等待的時(shí)長。
TIME_WAIT 有存在的意義,但是太多保持在這種狀態(tài)的連接會占用雙方資源,占據(jù)客戶端的端口資源和服務(wù)端的系統(tǒng)資源。
Linux 提供了 tcp_max_tw_buckets 參數(shù),當(dāng) TIME_WAIT 的連接數(shù)量超過該參數(shù)時(shí),新關(guān)閉的連接就不再經(jīng)歷 TIME_WAIT 而直接關(guān)閉。這個(gè)參數(shù)的設(shè)定應(yīng)該取一個(gè)平衡點(diǎn),即既不會太少導(dǎo)致高并發(fā)時(shí)產(chǎn)生連接間數(shù)據(jù)錯(cuò)亂的問題,也不會太多而導(dǎo)致耗盡端口和線程資源。

對于用戶端來講,還可以啟用 tcp_tw_reuse 參數(shù)來復(fù)用處于 TIME_WAIT 狀態(tài)的連接(來節(jié)約接口資源。)這個(gè)參數(shù)有幾個(gè)前提,一個(gè)是只有客戶端可以打開,一個(gè)是 TIME_WAIT 狀態(tài)也要保持 1 秒,另一個(gè)是要同步打開時(shí)間戳功能,報(bào)文帶上時(shí)間戳就可以避免沒有了 2MSL 時(shí)長以后的混亂情況,時(shí)間戳過期的報(bào)文就會被丟掉。

另外對于 TIME_WAIT,還可以調(diào)整 socket 選項(xiàng),來達(dá)到調(diào)用 close 關(guān)閉連接時(shí)跳過四次揮手直接關(guān)閉的效果,但不推薦。

2. 被動方的優(yōu)化
首先,被動方收到 FIN 時(shí),會自動回復(fù) ACK,接著等待應(yīng)用程序調(diào)用 close/shutdown 來結(jié)束連接,再發(fā)送 FIN。如果系統(tǒng)中同時(shí)查看到多個(gè)連接處于 CLOSE_WAIT 狀態(tài),則需要排查是否是應(yīng)用程序出了故障。
然后,當(dāng)被動方也發(fā)送了 FIN 以后,還需要等待主動方回復(fù)一個(gè) ACK,如果遲遲沒收到,也會重發(fā) FIN,重發(fā)次數(shù)也是 tcp_orphan_retries 參數(shù)控制,這點(diǎn)和主動方的優(yōu)化一致,可以調(diào)整次數(shù)。(需確認(rèn)被動方是否有 tcp_max_orphans 參數(shù))

3. 如果雙方同時(shí)關(guān)閉?

同時(shí)關(guān)閉

如圖所示,雙方就都會有 FIN_WAIT1 和 CLOSE_WAIT 這兩種狀態(tài),只要針對上述提到過的這兩種狀態(tài)進(jìn)行優(yōu)化即可。
同時(shí),雙方在期待收到 ACK 的時(shí)候收到的是 FIN,這時(shí)會進(jìn)入 CLOSING 狀態(tài),替代 FIN_WAIT2 的狀態(tài)。(需確認(rèn) CLOSING 是否和 FIN_WAIT2 完全一致)

四、傳輸數(shù)據(jù)中的優(yōu)化

1. ACK 延遲
目前在 TCP 中每傳輸一個(gè)報(bào)文都要求接收方進(jìn)行確認(rèn),大量短而頻繁的確認(rèn)報(bào)文給網(wǎng)絡(luò)帶來了很多開銷。因此采取了延遲 ACK 策略來減少 ACK 的數(shù)量,就是接收方收到一個(gè)報(bào)文以后,不會立即發(fā)送 ACK,而是等待 1~200ms,這期間若有回送數(shù)據(jù)報(bào)文就捎帶確認(rèn),但收到兩個(gè)連續(xù)數(shù)據(jù)報(bào)文或者等待超時(shí)則發(fā)送一個(gè)獨(dú)立確認(rèn)。有效減少了 ACK 的數(shù)量,改善了 TCP 的整體性能。

2. 滑動窗口
接收方的接收緩沖區(qū)不是不變的,接收到新的會變小,應(yīng)用程序取出后又會變大,因此接收方會把自己當(dāng)前的接收窗口大小放在 TCP 頭告知發(fā)送方,如果不考慮擁塞控制,發(fā)送方的窗口大小「約等于」接收方的窗口大小。
對于這一點(diǎn),可以把 tcp_window_scaling 配置設(shè)為 1(默認(rèn)打開)來擴(kuò)大 TCP 通告窗口至 1G 大小。要使用這一選項(xiàng),需要主動方在 SYN 中先告知,被動方在 SYN 中再反饋。
但是緩沖區(qū)并非越大越好,還要考慮網(wǎng)絡(luò)吞吐的能力。如果緩沖區(qū)與網(wǎng)絡(luò)傳輸能力匹配,那么緩沖區(qū)的利用率就達(dá)到了最大化。

3. 調(diào)整緩沖區(qū)大小
這里需要說一個(gè)概念,就是帶寬時(shí)延積,它決定網(wǎng)絡(luò)中飛行報(bào)文的大小,它的計(jì)算方式:

帶寬時(shí)延積

比如最大帶寬是 100 MB/s,網(wǎng)絡(luò)時(shí)延(RTT)是 10ms 時(shí),意味著客戶端到服務(wù)端的網(wǎng)絡(luò)一共可以存放 100MB/s * 0.01s = 1MB 的字節(jié)。這個(gè) 1MB 是帶寬和時(shí)延的乘積,所以它就叫「帶寬時(shí)延積」(縮寫為 BDP,Bandwidth Delay Product)。同時(shí),這 1MB 也表示「飛行中」的 TCP 報(bào)文大小,它們就在網(wǎng)絡(luò)線路、路由器等網(wǎng)絡(luò)設(shè)備上。如果飛行報(bào)文超過了 1 MB,就會導(dǎo)致網(wǎng)絡(luò)過載,容易丟包。
因此,緩沖區(qū)的大小越靠近這個(gè)帶寬時(shí)延積越好。

(1)發(fā)送緩沖區(qū)的調(diào)整
發(fā)送緩沖區(qū)是自行調(diào)節(jié)的,當(dāng)發(fā)送方發(fā)送的數(shù)據(jù)被確認(rèn)后,并且沒有新的數(shù)據(jù)要發(fā)送,就會把發(fā)送緩沖區(qū)的內(nèi)存釋放掉。
接收緩沖區(qū)要復(fù)雜一些:

接收緩沖區(qū)

上面三個(gè)數(shù)字單位都是字節(jié),它們分別表示:

  • 第一個(gè)數(shù)值是動態(tài)范圍的最小值,表示即使在內(nèi)存壓力下也可以保證的最小接收緩沖區(qū)大小,4096 byte = 4K;(內(nèi)存壓力下,緩沖區(qū)仍然可以接收新的 4k 數(shù)據(jù)并取出運(yùn)算)
  • 第二個(gè)數(shù)值是初始默認(rèn)值,87380 byte ≈ 86K;
  • 第三個(gè)數(shù)值是動態(tài)范圍的最大值,6291456 byte = 6144K(6M);

(2)接收緩沖區(qū)的調(diào)整
接收緩沖區(qū)可以根據(jù)系統(tǒng)空閑內(nèi)存的大小來調(diào)節(jié)接收窗口:

  • 如果系統(tǒng)的空閑內(nèi)存很多,就可以自動把緩沖區(qū)增大一些,這樣傳給對方的接收窗口也會變大,因而提升發(fā)送方發(fā)送的傳輸數(shù)據(jù)數(shù)量;
  • 反正,如果系統(tǒng)的內(nèi)存很緊張,就會減少緩沖區(qū),這雖然會降低傳輸效率,可以保證更多的并發(fā)連接正常工作;(否則緩沖區(qū)已接收很多的數(shù)據(jù)讀寫請求,但是系統(tǒng)內(nèi)存資源不足,無法讓應(yīng)用程序正常取出數(shù)據(jù)并計(jì)算,就會出問題)

(3)內(nèi)存的判斷
那么如何判斷內(nèi)存緊張或充分呢?

調(diào)整 TCP 內(nèi)存范圍

上面三個(gè)數(shù)字單位不是字節(jié),而是「頁面大小」,1 頁表示 4KB,它們分別表示:

  • 當(dāng) TCP 內(nèi)存小于第 1 個(gè)值時(shí),不需要進(jìn)行自動調(diào)節(jié);
  • 在第 1 和第 2 個(gè)值之間時(shí),內(nèi)核開始調(diào)節(jié)接收緩沖區(qū)的大??;
  • 大于第 3 個(gè)值時(shí),內(nèi)核不再為 TCP 分配新內(nèi)存,此時(shí)新連接是無法建立的;
    一般情況下,這些值是在系統(tǒng)啟動時(shí)根據(jù)系統(tǒng)內(nèi)存數(shù)量計(jì)算得到的。

在實(shí)際的場景中,TCP 緩沖區(qū)最小值保持默認(rèn) 4K 即可,來提高并發(fā)處理能力;最大值則盡可能靠近帶寬時(shí)延積,來最大化網(wǎng)絡(luò)效率。

總結(jié)以上:為了提高并發(fā)能力、提高網(wǎng)絡(luò)效率,我們要充分利用網(wǎng)絡(luò)能力和自己的內(nèi)存。網(wǎng)絡(luò)這方面就是將緩沖區(qū)大小的極值盡可能靠近帶寬時(shí)延積,而同時(shí)對緩沖區(qū)的自動調(diào)節(jié)需要結(jié)合內(nèi)存來判斷,這個(gè) TCP 內(nèi)存的判斷是通過系統(tǒng)內(nèi)存計(jì)算出來的幾個(gè)值來劃分的,在不同區(qū)間會對分配給緩沖區(qū)的內(nèi)存大小進(jìn)行調(diào)整。

以上就是 TCP 在不同階段的優(yōu)化策略和思路,有關(guān)擁塞控制和流量控制之后再補(bǔ)一篇筆記。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容