高併發場景下如何優化伺服器的效能?

冰河團隊發表於2021-01-17

寫在前面

最近,有小夥伴在群裡提問:Linux系統怎麼設定tcp_nodelay引數?也有小夥伴說問我。那今天,我們就來根據這個問題來聊聊在高併發場景下如何優化伺服器的效能這個話題。

其實,tcp_nodelay引數並不是在作業系統級別進行配置的,而是在TCP套接字上新增tcp_nodelay引數來關閉粘包演算法,以便使資料包能夠立即投遞出去。tcp_nodelay引數主要是對TCP套接字來說的,那對於伺服器硬體,如果要使其能夠支撐上百萬甚至上千萬的併發,我們該如何對其進行優化呢?

文章已收錄到:

https://github.com/sunshinelyz/technology-binghe

https://gitee.com/binghe001/technology-binghe

作業系統

這裡,我使用的作業系統為CentOS 8,我們可以輸入如下命令來檢視作業系統的版本。

CentOS Linux release 8.0.1905 (Core) 

對於高併發的場景,我們主要還是優化作業系統的網路效能,而作業系統中,有很多關於網路協議的引數,我們對於伺服器網路效能的優化,主要是對這些系統引數進行調優,以達到提升我們應用訪問效能的目的。

系統引數

在CentOS 作業系統中,我們可以通過如下命令來檢視所有的系統引數。

/sbin/sysctl -a

部分輸出結果如下所示。

這裡的引數太多了,大概有一千多個,在高併發場景下,我們不可能對作業系統的所有引數進行調優。我們更多的是關注與網路相關的引數。如果想獲得與網路相關的引數,那麼,我們首先需要獲取作業系統引數的型別,如下命令可以獲取作業系統引數的型別。

/sbin/sysctl -a|awk -F "." '{print $1}'|sort -k1|uniq

執行命令輸出的結果資訊如下所示。

abi
crypto
debug
dev
fs
kernel
net
sunrpc
user
vm

其中的net型別就是我們要關注的與網路相關的作業系統引數。我們可以獲取net型別下的子型別,如下所示。

/sbin/sysctl -a|grep "^net."|awk -F "[.| ]" '{print $2}'|sort -k1|uniq

輸出的結果資訊如下所示。

bridge
core
ipv4
ipv6
netfilter
nf_conntrack_max
unix

在Linux作業系統中,這些與網路相關的引數都可以在/etc/sysctl.conf 檔案裡修改,如果/etc/sysctl.conf 檔案中不存在這些引數,我們可以自行在/etc/sysctl.conf 檔案中新增這些引數。

在net型別的子型別中,我們需要重點關注的子型別有:core和ipv4。

優化套接字緩衝區

如果伺服器的網路套接字緩衝區太小,就會導致應用程式讀寫多次才能將資料處理完,這會大大影響我們程式的效能。如果網路套接字緩衝區設定的足夠大,從一定程度上能夠提升我們程式的效能。

我們可以在伺服器的命令列輸入如下命令,來獲取有關伺服器套接字緩衝區的資訊。

/sbin/sysctl -a|grep "^net."|grep "[r|w|_]mem[_| ]"

輸出的結果資訊如下所示。

net.core.rmem_default = 212992
net.core.rmem_max = 212992
net.core.wmem_default = 212992
net.core.wmem_max = 212992
net.ipv4.tcp_mem = 43545        58062   87090
net.ipv4.tcp_rmem = 4096        87380   6291456
net.ipv4.tcp_wmem = 4096        16384   4194304
net.ipv4.udp_mem = 87093        116125  174186
net.ipv4.udp_rmem_min = 4096
net.ipv4.udp_wmem_min = 4096

其中,帶有max、default、min關鍵字的為分別代表:最大值、預設值和最小值;帶有mem、rmem、wmem關鍵字的分別為:總記憶體、接收緩衝區記憶體、傳送緩衝區記憶體。

這裡需要注意的是:帶有rmem 和 wmem關鍵字的單位都是“位元組”,而帶有mem關鍵字的單位是“頁”。“頁”是作業系統管理記憶體的最小單位,在 Linux 系統裡,預設一頁是 4KB 大小。

如何優化頻繁收發大檔案

如果在高併發場景下,需要頻繁的收發大檔案,我們該如何優化伺服器的效能呢?

這裡,我們可以修改的系統引數如下所示。

net.core.rmem_default
net.core.rmem_max
net.core.wmem_default
net.core.wmem_max
net.ipv4.tcp_mem
net.ipv4.tcp_rmem
net.ipv4.tcp_wmem

這裡,我們做個假設,假設系統最大可以給TCP分配 2GB 記憶體,最小值為 256MB,壓力值為 1.5GB。按照一頁為 4KB 來計算, tcp_mem 的最小值、壓力值、最大值分別是 65536、393216、524288,單位是“頁” 。

假如平均每個檔案資料包為 512KB,每個套接字讀寫緩衝區最小可以各容納 2 個資料包,預設可以各容納 4 個資料包,最大可以各容納 10 個資料包,那我們可以算出 tcp_rmem 和 tcp_wmem 的最小值、預設值、最大值分別是 1048576、2097152、5242880,單位是“位元組”。而 rmem_default 和 wmem_default 是 2097152,rmem_max 和 wmem_max 是 5242880。

注:後面詳細介紹這些數值是如何計算的~~

這裡,還需要注意的是:緩衝區超過了 65535,還需要將 net.ipv4.tcp_window_scaling 引數設定為 1。

經過上面的分析後,我們最終得出的系統調優引數如下所示。

net.core.rmem_default = 2097152
net.core.rmem_max = 5242880
net.core.wmem_default = 2097152
net.core.wmem_max = 5242880
net.ipv4.tcp_mem = 65536  393216  524288
net.ipv4.tcp_rmem = 1048576  2097152  5242880
net.ipv4.tcp_wmem = 1048576  2097152  5242880

優化TCP連線

對計算機網路有一定了解的小夥伴都知道,TCP的連線需要經過“三次握手”和“四次揮手”的,還要經過慢啟動、滑動視窗、粘包演算法等支援可靠性傳輸的一系列技術支援。雖然,這些能夠保證TCP協議的可靠性,但有時這會影響我們程式的效能。

那麼,在高併發場景下,我們該如何優化TCP連線呢?

(1)關閉粘包演算法

如果使用者對於請求的耗時很敏感,我們就需要在TCP套接字上新增tcp_nodelay引數來關閉粘包演算法,以便資料包能夠立刻傳送出去。此時,我們也可以設定net.ipv4.tcp_syncookies的引數值為1。

(2)避免頻繁的建立和回收連線資源

網路連線的建立和回收是非常消耗效能的,我們可以通過關閉空閒的連線、重複利用已經分配的連線資源來優化伺服器的效能。重複利用已經分配的連線資源大家其實並不陌生,像:執行緒池、資料庫連線池就是複用了執行緒和資料庫連線。

我們可以通過如下引數來關閉伺服器的空閒連線和複用已分配的連線資源。

net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_keepalive_time=1800

(3)避免重複傳送資料包

TCP支援超時重傳機制。如果傳送方將資料包已經傳送給接收方,但傳送方並未收到反饋,此時,如果達到設定的時間間隔,就會觸發TCP的超時重傳機制。為了避免傳送成功的資料包再次傳送,我們需要將伺服器的net.ipv4.tcp_sack引數設定為1。

(4)增大伺服器檔案描述符數量

在Linux作業系統中,一個網路連線也會佔用一個檔案描述符,連線越多,佔用的檔案描述符也就越多。如果檔案描述符設定的比較小,也會影響我們伺服器的效能。此時,我們就需要增大伺服器檔案描述符的數量。

例如:fs.file-max = 10240000,表示伺服器最多可以開啟10240000個檔案。

好了,本文結合群內讀者的提問進行的一些總結,希望能夠給小夥伴們帶來實質性的幫助。今天就到這兒吧,我是冰河,大家有啥問題可以在下方留言,也可以加我微信:sun_shine_lyz,一起交流技術,一起進階,一起牛逼~~

相關文章