Linux 服务器

Linux 高并发优化:调大 ulimit 文件句柄、sysctl TCP 参数调优,解决“nf_conntrack: table full”

6 个步骤 12 分钟阅读 8 次阅读 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 中命令的输出结果,方便工程师快速定位。

这篇文章有帮助吗?

相关教程