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