想要支持百萬長連接,需要調(diào)優(yōu)哪些參數(shù)?
文件描述符限制
- 系統(tǒng)級別限制:操作系統(tǒng)會設(shè)置一個全局的文件描述符限制,控制整個系統(tǒng)能同時打開的最大文件數(shù)
- 用戶級別限制:每個用戶會有一個文件描述符的限制,控制這個用戶能夠同時打開的最大文件數(shù)
- 進程級別限制:每個進程也會有一個文件描述符的限制,控制單個進程能夠同時打開的最大文件數(shù)
服務(wù)器 TCP 連接數(shù)量上限
一個服務(wù)端的 TCP 網(wǎng)絡(luò)應(yīng)用,理論上可以支持的最大連接數(shù)量是多少?
其中服務(wù)端 IP、服務(wù)端 Port 已經(jīng)固定了 (就是監(jiān)聽的 TCP 程序),所以理論的連接數(shù)量上限就取決于 (客戶端 IP * 客戶端 Port) 的組合數(shù)量了。
當然如果服務(wù)端程序監(jiān)聽 1 ~ 65535 的所有端口號,理論的連接數(shù)量上限就變?yōu)?
當然實際情況下肯定達不到這樣的上限數(shù)量,原因有三:
- IP 地址中有分類地址 (A, B, C 類)、內(nèi)網(wǎng)地址、保留地址 (D, E 類),其中后兩者無法用于公網(wǎng)通信
- 某些端口會被保留,僅供專門程序使用,例如 DNS (53), HTTPS (443)
- 服務(wù)器內(nèi)存大小有上限,一個 TCP 套接字會關(guān)聯(lián)內(nèi)存緩沖區(qū)、文件描述符等資源
綜上所述,一個服務(wù)端的 TCP 網(wǎng)絡(luò)應(yīng)用,可以支持的最大連接數(shù)量主要取決于其內(nèi)存大小 (內(nèi)核參數(shù)都已經(jīng)調(diào)優(yōu)的情況下)。
如何測試?
在測試設(shè)備不充足的情況下,如何測試百萬連接數(shù)量場景?核心思路:突破 TCP 四元組限制即可。
- 客戶端配置多個 IP, 這樣每個 IP 地址就有大約 64K 個端口號可以使用,向服務(wù)端發(fā)起連接之前,綁定不同的 IP 地址 即可
- 服務(wù)端監(jiān)聽多個端口號,客戶端只需要連接不同的服務(wù)端號口即可
too many open files
首先來看一個高并發(fā)場景下的 “經(jīng)典問題”: too many open files, 產(chǎn)生這個問題的根本原因是: 短時間內(nèi)打開大量網(wǎng)絡(luò) (文件) 連接,超過了操作系統(tǒng)對單個進程允許打開的文件描述符(file descriptor)數(shù)量限制。
想要單機支持 100 萬鏈接,需要調(diào)優(yōu)哪些參數(shù)呢?
解決方案
Soft open files 是 Linux 系統(tǒng)參數(shù),影響系統(tǒng)單個進程能夠打開最大的文件句柄數(shù)量。
$ ulimit -n
# 默認輸出 1024 或者 65535
1024
表示單個進程同時最多只能維持 1024 個網(wǎng)絡(luò) (例如 TCP) 連接。
可以通過增大該參數(shù),來支持更大的網(wǎng)絡(luò)連接數(shù)量。
(1) 臨時性調(diào)整
只在當前會話 (終端) 中有效,退出或重啟后失效
$ ulimit -HSn 1048576
(2) 永久性設(shè)置
修改配置文件 /etc/security/limits.conf:
$ sudo vim /etc/security/limits.conf
# 追加如下內(nèi)容 (例如支持百萬連接)
# 重啟永久生效
# 單個進程可以打開的最大進程數(shù)量
# 表示可以針對不同用戶配置不同的值
# 當然實際情況中,網(wǎng)絡(luò)應(yīng)用一般會獨享整個主機/容器所有資源
# 調(diào)整文件描述符限制
# 注意: 實際生效時會以兩者中的較小值為準 (所以最好的方法就是保持兩個值相同)
* soft nofile 1048576
* hard nofile 1048576
root soft nofile 1048576
root hard nofile 1048576
運行 sysctl -p 命令生效,重啟之后仍然有效。
(3) 其他設(shè)置
單個進程打開的文件描述符數(shù)量 不能超過 操作系統(tǒng)所有進程文件描述符數(shù)量 (/proc/sys/fs/file-max), 所以需要修改對應(yīng)的值:
$ sudo vim /etc/sysctl.conf
# 操作系統(tǒng)所有進程一共可以打開的文件數(shù)量
# 增加/修改以下內(nèi)容
# 注意: 該設(shè)置只對非 root 用戶進行限制, root 不受影響
fs.file-max = 16777216
# 進程級別可以打開的文件數(shù)量
# 或者可以設(shè)置為一個比 soft nofile 和 hard nofile 略大的值
fs.nr_open = 16777216
運行 sysctl -p 命令生效,重啟之后仍然有效。
(4) 查看配置
$ cat /proc/sys/fs/file-nr
# 第一個數(shù)表示當前系統(tǒng)使用的文件描述符數(shù)
# 第二個數(shù)表示分配后已釋放的文件描述符數(shù)
# 第三個數(shù)等于 file-max
1344 0 1048576
Linux 內(nèi)核參數(shù)調(diào)優(yōu)
想要單機支持 100 萬鏈接,除了剛才的 文件描述符數(shù)量 參數(shù)調(diào)優(yōu)之外,還需要針對部分內(nèi)核參數(shù)進行調(diào)優(yōu)。
打開系統(tǒng)配置文件 /etc/sysctl.conf,增加 (或修改) 以下配置數(shù)據(jù),參數(shù)名稱及其作用已經(jīng)寫在了注釋中。
# 設(shè)置系統(tǒng)的 TCP TIME_WAIT 數(shù)量,如果超過該值
# 不需要等待 2MSL,直接關(guān)閉
net.ipv4.tcp_max_tw_buckets = 1048576
# 將處于 TIME_WAIT 狀態(tài)的套接字重用于新的連接
# 如果新連接的時間戳 大于 舊連接的最新時間戳
# 重用該狀態(tài)下的現(xiàn)有 TIME_WAIT 連接,這兩個參數(shù)主要針對接收方 (服務(wù)端)
# 對于發(fā)送方 (客戶端) ,這兩個參數(shù)沒有任何作用
net.ipv4.tcp_tw_reuse = 1
# 必須配合使用
net.ipv4.tcp_timestamps = 1
# 啟用快速回收 TIME_WAIT 資源
# net.ipv4.tcp_tw_recycle = 1
# 能夠更快地回收 TIME_WAIT 套接字
# 此選項會導(dǎo)致處于 NAT 網(wǎng)絡(luò)的客戶端超時,建議設(shè)置為 0
# 因為當來自同一公網(wǎng) IP 地址的不同主機嘗試與服務(wù)器建立連接時,服務(wù)器會因為時間戳的不匹配而拒絕新的連接
# 這是因為內(nèi)核會認為這些連接是舊連接的重傳
# 該配置會在 Linux/4.12 被移除
# 在之后的版本中查看/設(shè)置會提示 "cannot stat /proc/sys/net/ipv4/tcp_tw_recycle"
# net.ipv4.tcp_tw_recycle = 0
# 縮短 Keepalive 探測失敗后,連接失效之前發(fā)送的保活探測包數(shù)量
net.ipv4.tcp_keepalive_probes = 3
# 縮短發(fā)送 Keepalive 探測包的間隔時間
net.ipv4.tcp_keepalive_intvl = 15
# 縮短最后一次數(shù)據(jù)包到 Keepalive 探測包的間隔時間
# 減小 TCP 連接保活時間
# 決定了 TCP 連接在沒有數(shù)據(jù)傳輸時,多久發(fā)送一次保活探測包,以確保連接的另一端仍然存在
# 默認為 7200 秒
net.ipv4.tcp_keepalive_time = 600
# 控制 TCP 的超時重傳次數(shù),決定了在 TCP 連接丟失或沒有響應(yīng)的情況下,內(nèi)核重傳數(shù)據(jù)包的最大次數(shù)
# 如果超過這個次數(shù)仍未收到對方的確認包,TCP 連接將被終止
net.ipv4.tcp_retries2 = 10
# 縮短處于 TIME_WAIT 狀態(tài)的超時時間
# 決定了在發(fā)送 FIN(Finish)包之后,TCP 連接保持在 FIN-WAIT-2 狀態(tài)的時間 (對 FIN-WAIT-1 狀態(tài)無效)
# 主要作用是在 TCP 連接關(guān)閉時,為了等待對方關(guān)閉連接而保留資源的時間
# 如果超過這個時間仍未收到 FIN 包,連接將被關(guān)閉
# 更快地檢測和釋放無響應(yīng)的連接,釋放資源
net.ipv4.tcp_fin_timeout = 15
# 調(diào)整 TCP 接收和發(fā)送窗口的大小,以提高吞吐量
# 三個數(shù)值分別是 min,default,max,系統(tǒng)會根據(jù)這些設(shè)置,自動調(diào)整 TCP 接收 / 發(fā)送緩沖區(qū)的大小
net.ipv4.tcp_mem = 8388608 12582912 16777216
net.ipv4.tcp_rmem = 8192 87380 16777216
net.ipv4.tcp_wmem = 8192 65535 16777216
# 定義了系統(tǒng)中每一個端口監(jiān)聽隊列的最大長度
net.core.somaxconn = 65535
# 增加半連接隊列容量
# 除了系統(tǒng)參數(shù)外 (net.core.somaxconn, net.ipv4.tcp_max_syn_backlog)
# 程序設(shè)置的 backlog 參數(shù)也會影響,以三者中的較小值為準
net.ipv4.tcp_max_syn_backlog = 65535
# 全連接隊列已滿后,如何處理新到連接 ?
# 如果設(shè)置為 0 (默認情況)
# 客戶端發(fā)送的 ACK 報文會被直接丟掉,然后服務(wù)端重新發(fā)送 SYN+ACK (重傳) 報文
# 如果客戶端設(shè)置的連接超時時間比較短,很容易在這里就超時了,返回 connection timeout 錯誤,自然也就沒有下文了
# 如果客戶端設(shè)置的連接超時時間比較長,收到服務(wù)端的 SYN+ACK (重傳) 報文之后,會認為之前的 ACK 報文丟包了
# 于是再次發(fā)送 ACK 報文,也許可以等到服務(wù)端全連接隊列有空閑之后,建立連接完成
# 當服務(wù)端重試次數(shù)到達上限 (tcp_synack_retries) 之后,發(fā)送 RST 報文給客戶端
# 默認情況下,tcp_synack_retries 參數(shù)等于 5, 而且采用指數(shù)退避算法
# 也就是說,5 次的重試時間間隔為 1s, 2s, 4s, 8s, 16s, 總共 31s
# 第 5 次重試發(fā)出后還要等 32s 才能知道第 5 次重試也超時了,所以總共需要等待 1s + 2s + 4s+ 8s+ 16s + 32s = 63s
# 如果設(shè)置為 1
# 服務(wù)端直接發(fā)送 RST 報文給客戶端,返回 connection reset by peer
# 設(shè)置為 1, 可以避免服務(wù)端給客戶端發(fā)送 SYN+ACK
# 但是會帶來另外一個問題: 客戶端無法根據(jù) RST 報文判斷出,服務(wù)端拒絕的具體原因:
# 因為對應(yīng)的端口沒有應(yīng)用程序監(jiān)聽,還是全隊列滿了
# 除了系統(tǒng)參數(shù)外 (net.core.somaxconn)
# 程序設(shè)置的 backlog 參數(shù)也會影響,以兩者中的較小值為準
# 所以全連接隊列大小 = min(backlog, somaxconn)
net.ipv4.tcp_abort_on_overflow = 1
# 增大每個套接字的緩沖區(qū)大小
net.core.optmem_max = 81920
# 增大套接字接收緩沖區(qū)大小
net.core.rmem_max = 16777216
# 增大套接字發(fā)送緩沖區(qū)大小
net.core.wmem_max = 16777216
# 增加網(wǎng)絡(luò)接口隊列長度,可以避免在高負載情況下丟包
# 在每個網(wǎng)絡(luò)接口接收數(shù)據(jù)包的速率比內(nèi)核處理這些包的速率快時,允許送到隊列的數(shù)據(jù)包的最大數(shù)量
net.core.netdev_max_backlog = 65535
# 增加連接追蹤表的大小,可以支持更多的并發(fā)連接
# 注意:如果防火墻沒開則會提示 error: "net.netfilter.nf_conntrack_max" is an unknown key,忽略即可
net.netfilter.nf_conntrack_max = 1048576
# 縮短連接追蹤表中處于 TIME_WAIT 狀態(tài)連接的超時時間
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 30
運行 sysctl -p 命令生效,重啟之后仍然有效。
注意事項
如果系統(tǒng)已經(jīng)使用了參數(shù) net.ipv4.tcp_syncookies, 參數(shù) net.ipv4.tcp_max_syn_backlog 將自動失效。
客戶端參數(shù)
當服務(wù)器充當 “客戶端角色” 時 (例如代理服務(wù)器),連接后端服務(wù)器器時,每個連接需要分配一個臨時端口號。
# 查詢系統(tǒng)配置的臨時端口號范圍
$ sysctl net.ipv4.ip_local_port_range
# 增加系統(tǒng)配置的臨時端口號范圍
$ sysctl -w net.ipv4.ip_local_port_range="10000 65535"