Linux系統(tǒng)TCP網(wǎng)絡(luò)參數(shù)優(yōu)化技巧
一、前言
在Linux系統(tǒng)中,網(wǎng)絡(luò)參數(shù)優(yōu)化對(duì)于提升網(wǎng)絡(luò)性能和確保穩(wěn)定通信至關(guān)重要。
適當(dāng)?shù)木W(wǎng)絡(luò)配置可以減少延遲、提高吞吐量,并優(yōu)化資源利用率。例如,調(diào)整TCP窗口大小、隊(duì)列長(zhǎng)度和超時(shí)設(shè)置等參數(shù),能夠更好地適應(yīng)不同網(wǎng)絡(luò)環(huán)境和應(yīng)用需求,從而改善用戶體驗(yàn),特別是在高并發(fā)和大數(shù)據(jù)傳輸場(chǎng)景下。
此外,優(yōu)化網(wǎng)絡(luò)參數(shù)也有助于預(yù)防擁塞,增強(qiáng)網(wǎng)絡(luò)故障恢復(fù)能力,確保服務(wù)的可靠性和效率。
Linux網(wǎng)絡(luò)優(yōu)化最多的是傳輸層TCP相關(guān)的網(wǎng)絡(luò)優(yōu)化。以下為工作中的經(jīng)常遇到可能會(huì)調(diào)整Linux網(wǎng)絡(luò)協(xié)議棧參數(shù)。
二、傳輸層優(yōu)化
2.1 優(yōu)化TCP SYN重傳次數(shù)
當(dāng)我們的服務(wù)器作為Client, Client 會(huì)給 Server 發(fā)送一個(gè) SYN 包,但是該 SYN 包可能會(huì)在傳輸過(guò)程中丟失,或者因?yàn)槠渌驅(qū)е?Server 無(wú)法處理,此時(shí) Client 這一側(cè)就會(huì)觸發(fā)超時(shí)重傳機(jī)制。
tcp_syn_retries 的默認(rèn)值是 6,也就是說(shuō)如果 SYN 一直發(fā)送失敗,會(huì)在(1 + 2 + 4 + 8 + 16+ 32 + 64)秒,即 127 秒后產(chǎn)生 ETIMEOUT 的錯(cuò)誤。
#我們可以通過(guò)調(diào)小重傳次數(shù)來(lái)減少阻塞時(shí)間:
net.ipv4.tcp_syn_retries = 2
2.2 優(yōu)化半連接隊(duì)列(syn queue)
半連接,即收到了 SYN 后還沒(méi)有回復(fù) SYNACK 的連接,Server 每收到一個(gè)新的 SYN 包,都會(huì)創(chuàng)建一個(gè)半連接,然后把該半連接加入到半連接隊(duì)列(syn queue)中。
當(dāng)系統(tǒng)中積壓的半連接個(gè)數(shù)超過(guò)了該值后,新的 SYN 包就會(huì)被丟棄。對(duì)于服務(wù)器而言,可能瞬間會(huì)有非常多的新建連接,所以我們可以適當(dāng)?shù)卣{(diào)大該值,以免 SYN 包被丟棄而導(dǎo)致 Client 收不到 SYNACK。系統(tǒng)默認(rèn)值為128。
net.ipv4.tcp_max_syn_backlog = 16384
2.3 開(kāi)啟SYN Cookies
在 Server 收到 SYN 包時(shí),不去分配資源來(lái)保存 Client 的信息,而是根據(jù)這個(gè) SYN 包計(jì)算出一個(gè) Cookie 值,然后將 Cookie 記錄到 SYNACK 包中發(fā)送出去。對(duì)于正常的連接,該 Cookies 值會(huì)隨著 Client 的 ACK 報(bào)文被帶回來(lái)。然后 Server 再根據(jù)這個(gè) Cookie 檢查這個(gè) ACK 包的合法性,如果合法,才去創(chuàng)建新的 TCP 連接。
SYN Cookies 可以防止部分 SYN Flood 攻擊。所以對(duì)于 Linux 服務(wù)器而言,推薦開(kāi)啟 SYN Cookies。
net.ipv4.tcp_syncookies = 1
2.4 優(yōu)化TCP SYNACK的重傳次數(shù)
當(dāng)Client向Server端發(fā)送SYN報(bào)文后,Server會(huì)返回給Client SYNACK報(bào)文,當(dāng)Client沒(méi)有響應(yīng)Server ACK報(bào)文,則Server會(huì)進(jìn)行重傳,我們可以減少重傳次數(shù)。系統(tǒng)默認(rèn)值為5。
net.ipv4.tcp_synack_retries = 2
2.5 優(yōu)化全連連隊(duì)列(accept queue)
三次握手就完成了,即產(chǎn)生了一個(gè) TCP 全連接(complete),它會(huì)被添加到全連接隊(duì)列(accept queue)中。然后 Server 就會(huì)調(diào)用 accept() 來(lái)完成 TCP 連接的建立。
當(dāng)服務(wù)器中積壓的全連接個(gè)數(shù)超過(guò)隊(duì)列長(zhǎng)度后,新的全連接就會(huì)被丟棄掉。
全連接隊(duì)列(accept queue)的長(zhǎng)度是由 listen(sockfd, backlog) 這個(gè)函數(shù)里的 backlog 控制的,而該 backlog 的最大值則是 somaxconn。系統(tǒng)默認(rèn)值為1024。
net.core.somaxconn = 16384
2.6 全連接溢出是否通知客戶端
當(dāng)服務(wù)器中積壓的全連接個(gè)數(shù)超過(guò)該值后,新的全連接就會(huì)被丟棄掉。Server 在將新連接丟棄時(shí),有的時(shí)候需要發(fā)送 reset 來(lái)通知 Client,這樣 Client 就不會(huì)再次重試了。
不過(guò),默認(rèn)行為是直接丟棄不去通知 Client。至于是否需要給 Client 發(fā)送 reset,是由 tcp_abort_on_overflow 這個(gè)配置項(xiàng)來(lái)控制的,該值默認(rèn)為 0,即不發(fā)送 reset 給 Client。推薦也是將該值配置為 0。
因?yàn)镾erver 如果來(lái)不及 accept() 而導(dǎo)致全連接隊(duì)列滿,這往往是由瞬間有大量新建連接請(qǐng)求導(dǎo)致的,正常情況下 Server 很快就能恢復(fù),然后 Client 再次重試后就可以建連成功了。也就是說(shuō),將 tcp_abort_on_overflow 配置為 0,給了 Client 一個(gè)重試的機(jī)會(huì)。
net.ipv4.tcp_abort_on_overflow = 0
2.7 優(yōu)化FIN超時(shí)時(shí)間
- 當(dāng)Client向Server端發(fā)送FIN報(bào)文后,就會(huì)進(jìn)入FIN_WAIT_1狀態(tài)。
- 當(dāng)Server回復(fù)客戶端ACK報(bào)文后,Client就會(huì)進(jìn)入FIN_WAIT_2狀態(tài)。
TCP 進(jìn)入到這個(gè)狀態(tài)后,如果本端遲遲收不到對(duì)端的 FIN 包,那就會(huì)一直處于這個(gè)狀態(tài),于是就會(huì)一直消耗系統(tǒng)資源。Linux 為了防止這種資源的開(kāi)銷,設(shè)置了這個(gè)狀態(tài)的超時(shí)時(shí)間 tcp_fin_timeout,默認(rèn)為 60s,超過(guò)這個(gè)時(shí)間后就會(huì)自動(dòng)銷毀該連接。
對(duì)于數(shù)據(jù)中心內(nèi)部的機(jī)器而言,將它調(diào)整為 2s 足以。
net.ipv4.tcp_fin_timeout = 2
2.8 優(yōu)化TIME_WAIT
- 當(dāng)Client向Server端發(fā)送FIN報(bào)文后,就會(huì)進(jìn)入FIN_WAIT_1狀態(tài);
- 當(dāng)Server回復(fù)Client ACK報(bào)文后,Client就會(huì)進(jìn)入FIN_WAIT_2狀態(tài);
- 當(dāng)Client收到Server的FIN報(bào)文后,會(huì)回復(fù)Server ACK報(bào)文,此時(shí)Client進(jìn)入TIME_WAIT狀態(tài)。
TIME_WAIT 狀態(tài)存在的意義是:
- 最后發(fā)送的這個(gè) ACK 包可能會(huì)被丟棄掉或者有延遲,這樣對(duì)端就會(huì)再次發(fā)送 FIN 包。如果不維持 TIME_WAIT 這個(gè)狀態(tài),那么再次收到對(duì)端的 FIN 包后,本端就會(huì)回一個(gè) Reset 包,這可能會(huì)產(chǎn)生一些異常。
所以維持 TIME_WAIT 狀態(tài)一段時(shí)間,可以保障 TCP 連接正常斷開(kāi)。TIME_WAIT 的默認(rèn)存活時(shí)間在 Linux 上是 60s(TCP_TIMEWAIT_LEN),這個(gè)時(shí)間對(duì)于數(shù)據(jù)中心而言可能還是有些長(zhǎng)了,所以有的時(shí)候也會(huì)修改內(nèi)核做些優(yōu)化來(lái)減小該值,或者將該值設(shè)置為可通過(guò) sysctl 來(lái)調(diào)節(jié)。
在Linux的內(nèi)核中,TCP/IP協(xié)議的TIME-WAIT狀態(tài)持續(xù)60秒且無(wú)法修改。Alibaba Cloud Linux 2從內(nèi)核版本4.19.43-13.al7開(kāi)始,新增內(nèi)核接口用于修改TCP TIME-WAIT超時(shí)時(shí)間。
#配置TIME_WAIT 狀態(tài)最大個(gè)數(shù)。
- 默認(rèn)值為16384,對(duì)于數(shù)據(jù)中心而言,網(wǎng)絡(luò)是相對(duì)很穩(wěn)定的,基本不會(huì)存在 FIN 包的異常,所以建議將該值調(diào)小一些。
net.ipv4.tcp_max_tw_buckets = 10000
#配置TIME_WAIT 狀態(tài)端口復(fù)用。
- Client 關(guān)閉跟 Server 的連接后,也有可能很快再次跟 Server 之間建立一個(gè)新的連接,而由于 TCP 端口最多只有 65536 個(gè),如果不去復(fù)用處于 TIME_WAIT 狀態(tài)的連接,就可能在快速重啟應(yīng)用程序時(shí),出現(xiàn)端口被占用而無(wú)法創(chuàng)建新連接的情況。
net.ipv4.tcp_tw_reuse = 1
#選項(xiàng) tcp_tw_recycle 來(lái)控制 TIME_WAIT 狀態(tài)。
- 但是該選項(xiàng)是很危險(xiǎn)的,因?yàn)樗赡軙?huì)引起意料不到的問(wèn)題,比如可能會(huì)引起 NAT 環(huán)境下的丟包問(wèn)題。因?yàn)榇蜷_(kāi)該選項(xiàng)后引起了太多的問(wèn)題,所以新版本的內(nèi)核就索性刪掉了這個(gè)配置選項(xiàng)。默認(rèn)為關(guān)閉。
net.ipv4.tcp_tw_recycle = 0
2.9 優(yōu)化TCP發(fā)送緩沖區(qū)
緩存了要發(fā)出去的數(shù)據(jù)。如果發(fā)送緩沖區(qū)已滿,應(yīng)用程序的寫(xiě)操作就會(huì)被阻塞。
tcp_wmem 中這三個(gè)數(shù)字的含義分別為 min、default、max。TCP 發(fā)送緩沖區(qū)的大小會(huì)在 min 和 max 之間動(dòng)態(tài)調(diào)整,初始的大小是 default,這個(gè)動(dòng)態(tài)調(diào)整的過(guò)程是由內(nèi)核自動(dòng)來(lái)做的,應(yīng)用程序無(wú)法干預(yù)。自動(dòng)調(diào)整的目的,是為了在盡可能少的浪費(fèi)內(nèi)存的情況下來(lái)滿足發(fā)包的需要。
#默認(rèn)值為 4096 16384 4194304。
net.ipv4.tcp_wmem = 8192 65536 16777216
2.10 優(yōu)化套接字發(fā)送緩沖區(qū)
tcp_wmem 中的 max 不能超過(guò) net.core.wmem_max(套接字發(fā)送緩沖區(qū)) 這個(gè)配置項(xiàng)的值,如果超過(guò)了,TCP 發(fā)送緩沖區(qū)最大就是 net.core.wmem_max。
#默認(rèn)值為212992。
net.core.wmem_max = 16777216
2.11 優(yōu)化TCP接收緩沖區(qū)
#默認(rèn)值為4096 87380 6291456。
net.ipv4.tcp_rmem = 8192 65536 16777216
2.12 優(yōu)化套接字接收緩沖區(qū)
#默認(rèn)值為212992。
net.core.wmem_max = 16777216
2.13 優(yōu)化TCP內(nèi)存消耗限制
tcp_wmem 以及 wmem_max 的大小設(shè)置都是針對(duì)單個(gè) TCP 連接的,這兩個(gè)值的單位都是 Byte(字節(jié))。系統(tǒng)中可能會(huì)存在非常多的 TCP 連接,如果 TCP 連接太多,就可能導(dǎo)致內(nèi)存耗盡。因此,所有 TCP 連接消耗的總內(nèi)存也有限制。
與前兩個(gè)選項(xiàng)不同的是,該選項(xiàng)中這些值的單位是 Page(頁(yè)數(shù)),也就是 4K。它也有 3 個(gè)值:min、pressure、max。當(dāng)所有 TCP 連接消耗的內(nèi)存總和達(dá)到 max 后,也會(huì)因達(dá)到限制而無(wú)法再往外發(fā)包。
#默認(rèn)值為88053 117407 176106。
net.ipv4.tcp_mem = 8388608 12582912 16777216
2.14 優(yōu)化CPU輪詢處理數(shù)據(jù)包數(shù)量
數(shù)據(jù)包到達(dá)網(wǎng)卡后,就會(huì)觸發(fā)中斷(IRQ)來(lái)告訴 CPU 讀取這個(gè)數(shù)據(jù)包。但是在高性能網(wǎng)絡(luò)場(chǎng)景下,數(shù)據(jù)包的數(shù)量會(huì)非常大,如果每來(lái)一個(gè)數(shù)據(jù)包都要產(chǎn)生一個(gè)中斷,那 CPU 的處理效率就會(huì)大打折扣,所以就產(chǎn)生了 NAPI(New API)這種機(jī)制讓 CPU 一次性地去輪詢(poll)多個(gè)數(shù)據(jù)包,以批量處理的方式來(lái)提升效率,降低網(wǎng)卡中斷帶來(lái)的性能開(kāi)銷。
該控制選項(xiàng)的默認(rèn)值是 300,在網(wǎng)絡(luò)吞吐量較大的場(chǎng)景中,我們可以適當(dāng)?shù)卦龃笤撝担热缭龃蟮?600。增大該值可以一次性地處理更多的數(shù)據(jù)包。但是這種調(diào)整也是有缺陷的,因?yàn)檫@會(huì)導(dǎo)致 CPU 在這里 poll 的時(shí)間增加,如果系統(tǒng)中運(yùn)行的任務(wù)很多的話,其他任務(wù)的調(diào)度延遲就會(huì)增加。
net.core.netdev_budget = 600
三、網(wǎng)絡(luò)層優(yōu)化
3.1 優(yōu)化本地端口范圍
IP 層這里容易觸發(fā)問(wèn)題的地方是 net.ipv4.ip_local_port_range 這個(gè)配置選項(xiàng),它是指和其他服務(wù)器建立 IP 連接時(shí)本地端口(local port)的范圍。我們?cè)谏a(chǎn)環(huán)境中就遇到過(guò)默認(rèn)的端口范圍太小,以致于無(wú)法創(chuàng)建新連接的問(wèn)題。所以通常情況下,我們都會(huì)擴(kuò)大默認(rèn)的端口范圍。
#默認(rèn)值為 32768 60999:
net.ipv4.ip_local_port_range = 1024 65535
3.2 優(yōu)化 txqueuelen隊(duì)列
為了能夠?qū)?TCP/IP 數(shù)據(jù)流進(jìn)行流控,Linux 內(nèi)核在 IP 層實(shí)現(xiàn)了 qdisc(排隊(duì)規(guī)則)。
qdisc 的隊(duì)列長(zhǎng)度是我們用 ifconfig 來(lái)看到的 txqueuelen,我們?cè)谏a(chǎn)環(huán)境中也遇到過(guò)因?yàn)?txqueuelen 太小導(dǎo)致數(shù)據(jù)包被丟棄的情況,這類問(wèn)題可以通過(guò)下面這個(gè)命令來(lái)觀察。
ip -s -s link ls dev ens33
如果觀察到 dropped 這一項(xiàng)不為 0,那就有可能是 txqueuelen 太小導(dǎo)致的。當(dāng)遇到這種情況時(shí),你就需要增大該值了,比如增加 eth0 這個(gè)網(wǎng)絡(luò)接口的 txqueuelen:
ip link set eth0 txqueuelen 2000
#查看系統(tǒng) txqueuelen大小。
ip a