网站或 API 访问量上来之后,最先出现的报错往往与 CPU 或内存无关,而是 Too many open files、nf_conntrack: table full, dropping packet、连接卡在 SYN 状态,或者监听队列溢出。这些都是内核和进程层面的限制,其默认值适合小型机器,而不是要承载数万并发连接的服务器。本文介绍如何判断你触发的是哪项限制,以及如何提高文件描述符上限(ulimit)、用 sysctl 调优 TCP 协议栈、调整连接跟踪表大小,并相应地配置 Nginx。
# 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
每个连接、日志文件和 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
以下参数适合作为繁忙的 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; 才能用上更大的队列。
如果使用了 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 端口完全不做连接跟踪,但调大连接跟踪表是更简单的解决办法。
# /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
在流量高峰期重新运行步骤 1 中的命令。ListenOverflows 应不再增长,conntrack 计数应远低于上限,应用日志中的“too many open files”报错也应消失。如需持续监控 CPU、内存、磁盘和带宽,请参见服务器监控教程。
对于代理和 API,通常是服务的文件描述符上限和 conntrack 表;对于访客量会突然暴涨的网站,则是监听队列 backlog(somaxconn 加上 Nginx 中的 backlog)。通过步骤 1 就能判断你触发的是哪一项。
说明 conntrack 模块尚未加载。运行 modprobe nf_conntrack,并按照步骤 4 的方法将其加入 /etc/modules-load.d/。
CDN 能减少到达服务器的连接数,但源站仍然需要足够的文件描述符和 backlog 来承接 CDN 建立的连接,以及所有未被缓存的 API 或 WebSocket 流量。
如果按以上步骤操作后问题仍未解决,请提交工单联系 IMIDC 7×24 技术支持,并附上服务器 IP、系统版本、看到的报错信息以及步骤 1 中命令的输出结果,方便工程师快速定位。