Linux 伺服器

Linux 高併發最佳化:調大 ulimit 檔案控制代碼、sysctl TCP 引數調優,解決“nf_conntrack: table full”

6 個步驟 12 分鐘閱讀 11 次閱讀 0
本文目錄

網站或 API 訪問量上來之後,最先出現的報錯往往與 CPU 或記憶體無關,而是 Too many open files、nf_conntrack: table full, dropping packet、連線卡在 SYN 狀態,或者監聽佇列溢位。這些都是核心和程序層面的限制,其預設值適合小型機器,而不是要承載數萬併發連線的伺服器。本文介紹如何判斷你觸發的是哪項限制,以及如何提高檔案描述符上限(ulimit)、用 sysctl 調優 TCP 協議棧、調整連線跟蹤表大小,並相應地配置 Nginx。

每次只修改一項並觀察效果。這些引數只有在確實觸及限制時才有幫助,並不能讓本身就慢的應用變快。在 KVM VPS 和獨立伺服器上可以修改全部引數,但在容器(LXC/OpenVZ)內,很多核心引數是隻讀的。

步驟 1:確認觸發了哪項限制

# Kernel messages about dropped packets or full tables
dmesg -T | grep -iE 'conntrack|too many|possible SYN flooding' | tail

# Socket summary and listen queues (Recv-Q close to Send-Q = backlog is full)
ss -s
ss -lnt

# Listen queue overflows and dropped SYNs since boot
nstat -az | grep -iE 'ListenOverflows|ListenDrops|TCPReqQFull'

# Connection tracking usage vs maximum
cat /proc/sys/net/netfilter/nf_conntrack_count /proc/sys/net/netfilter/nf_conntrack_max 2>/dev/null

# Open file limit of the shell and of a running service (Nginx worker example)
ulimit -n
grep 'Max open files' /proc/$(pgrep -f 'nginx: worker' | head -1)/limits

步驟 2:提高檔案描述符上限

每個連線、日誌檔案和 socket 都會佔用一個檔案描述符。常見的預設值是每個程序 1024,對於繁忙的代理或資料庫來說遠遠不夠。需要提高系統級上限、登入會話的使用者級限制,以及最重要的 systemd 服務自身的限制,因為 systemd 服務不會讀取 /etc/security/limits.conf。

# System-wide maximum
cat > /etc/sysctl.d/90-files.conf <<'EOF2'
fs.file-max = 2097152
fs.nr_open = 2097152
EOF2

# Login sessions (takes effect at the next login)
cat > /etc/security/limits.d/90-nofile.conf <<'EOF2'
*    soft nofile 1048576
*    hard nofile 1048576
root soft nofile 1048576
root hard nofile 1048576
EOF2

# A specific service, e.g. Nginx (repeat for mysqld, php-fpm, your app)
mkdir -p /etc/systemd/system/nginx.service.d
cat > /etc/systemd/system/nginx.service.d/limits.conf <<'EOF2'
[Service]
LimitNOFILE=1048576
EOF2
systemctl daemon-reload && systemctl restart nginx

步驟 3:用 sysctl 調優 TCP 協議棧

以下引數適合作為繁忙的 Web、代理或 API 伺服器的初始配置。建議單獨寫入一個檔案,便於檢視和回滾改動。

cat > /etc/sysctl.d/91-network-tuning.conf <<'EOF2'
# Longer accept and SYN queues for bursts of new connections
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.core.netdev_max_backlog = 16384

# More local ports for outgoing connections (proxies, crawlers, API clients)
net.ipv4.ip_local_port_range = 1024 65000
# Reuse TIME_WAIT sockets for new outgoing connections (safe; tcp_tw_recycle no longer exists)
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15

# Keepalive: detect dead peers after about 10 minutes instead of 2 hours
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 5

# Keep SYN cookies on as a basic protection against SYN floods
net.ipv4.tcp_syncookies = 1

# Bigger socket buffers for fast long-distance transfers
net.core.rmem_max = 33554432
net.core.wmem_max = 33554432
net.ipv4.tcp_rmem = 4096 87380 33554432
net.ipv4.tcp_wmem = 4096 65536 33554432

# BBR congestion control (kernel 4.9+)
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
EOF2

sysctl --system
sysctl net.core.somaxconn net.ipv4.tcp_congestion_control

關於 BBR 的更多介紹,參見 Linux 開啟 BBR 教程。注意,應用程式會向 listen() 傳入自己的 backlog 值;例如 Nginx 需要配置 listen 80 backlog=65535; 才能用上更大的佇列。

步驟 4:調整連線跟蹤表(conntrack)大小

如果使用了 firewalld、ufw、iptables NAT 或 Docker,核心會在 conntrack 表中跟蹤每一個連線。表滿之後新資料包會被丟棄,網站表現為“時好時壞”、隨機無法訪問。預設上限取決於記憶體大小,通常只有 65536 或 262144。

# Load the module so the keys exist, and raise the limits
modprobe nf_conntrack
cat > /etc/sysctl.d/92-conntrack.conf <<'EOF2'
net.netfilter.nf_conntrack_max = 1048576
# Forget idle established connections after 2 hours instead of 5 days
net.netfilter.nf_conntrack_tcp_timeout_established = 7200
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 30
EOF2
sysctl --system

# Hash table size (about max / 4), applied at module load
echo 'options nf_conntrack hashsize=262144' > /etc/modprobe.d/nf_conntrack.conf
echo 262144 > /sys/module/nf_conntrack/parameters/hashsize

# Load the module at boot so the sysctl values are applied
echo nf_conntrack > /etc/modules-load.d/nf_conntrack.conf

每條 conntrack 記錄大約佔用 300 位元組核心記憶體,因此一百萬條約需 300 MB。對於不使用 NAT 的純 Web 伺服器,也可以用 notrack 規則讓 80/443 埠完全不做連線跟蹤,但調大連線跟蹤表是更簡單的解決辦法。

步驟 5:相應調整 Nginx 配置

# /etc/nginx/nginx.conf (main context and events block)
worker_processes auto;
worker_rlimit_nofile 1048576;

events {
    worker_connections 65535;
    multi_accept on;
}

http {
    keepalive_timeout 30;
    keepalive_requests 1000;
    # ...
}

# In each server block, let the listen socket use the bigger backlog
#   listen 80 backlog=65535;
#   listen 443 ssl backlog=65535;
nginx -t && systemctl reload nginx
grep 'Max open files' /proc/$(pgrep -f 'nginx: worker' | head -1)/limits

步驟 6:在高負載下驗證效果

在流量高峰期重新執行步驟 1 中的命令。ListenOverflows 應不再增長,conntrack 計數應遠低於上限,應用日誌中的“too many open files”報錯也應消失。如需持續監控 CPU、記憶體、磁碟和頻寬,請參見伺服器監控教程。

常見問題

哪個引數最關鍵?

對於代理和 API,通常是服務的檔案描述符上限和 conntrack 表;對於訪客量會突然暴漲的網站,則是監聽佇列 backlog(somaxconn 加上 Nginx 中的 backlog)。通過步驟 1 就能判斷你觸發的是哪一項。

sysctl 提示“cannot stat /proc/sys/net/netfilter/nf_conntrack_max”?

說明 conntrack 模組尚未載入。執行 modprobe nf_conntrack,並按照步驟 4 的方法將其加入 /etc/modules-load.d/。

使用了 Cloudflare 或 CDN,還需要這些設定嗎?

CDN 能減少到達伺服器的連線數,但源站仍然需要足夠的檔案描述符和 backlog 來承接 CDN 建立的連線,以及所有未被快取的 API 或 WebSocket 流量。

如果按以上步驟操作後問題仍未解決,請提交工單聯絡 IMIDC 7×24 技術支援,並附上伺服器 IP、系統版本、看到的報錯資訊以及步驟 1 中命令的輸出結果,方便工程師快速定位。

這篇文章有幫助嗎?

相關教程