HOOOS

高并发下 nf_conntrack: table full 报错?教你精准计算内核参数,拒绝内存崩溃

0 10 运维架构说 Linux高并发性能调优
Apple

在高并发网络压力测试或遭遇 DDoS 攻击时,Linux 服务器的 dmesg 或者是 /var/log/messages 经常会爆出这样一条红字警告:

nf_conntrack: table full, dropping packet

紧接着,服务器开始大面积丢包,网络请求超时,但 CPU 和内存利用率看起来却还挺空闲。

很多网上的教程会简单粗暴地告诉你:“把 nf_conntrack_max 改大就行了!”。但这往往是灾难的开始。 如果盲目将该值调得过大,而服务器内存不足,一旦并发真的冲上去,内核会瞬间吃光物理内存,触发 OOM Killer 杀掉你的核心服务(如 Nginx、Redis),甚至直接导致系统挂起。

如何在解决连接跟踪表满的同时,保证内存绝对安全?这需要一套基于数学计算的精准调优方案。


一、 为什么不能盲目放大?先算一笔系统账

nf_conntrack 是 Linux 内核连接跟踪(Connection Tracking)模块。只要启用了建设在连接跟踪之上的服务(如 iptables NAT、Firewalld、Docker 容器网络等),每一个经过服务器的 IP 包,都会在内核内存中创建一个 nf_conntrack 结构体来记录其状态。

一个 conntrack 结构体在 64 位 Linux 系统中,大约占用 320 字节(随着内核版本微调,通常在 300~380 字节之间,我们按 320 字节估算)。

如果盲目将 nf_conntrack_max 设置为 10,000,000(一千万):
$$10,000,000 \times 320 \text{ Bytes} \approx 3,200,000,000 \text{ Bytes} \approx 3 \text{ GB}$$

这意味着,一旦极端并发来临,光是内核网络连接跟踪这一个模块,就要强行吃掉 3GB 的物理内存。这部分内存属于内核 Slab 缓存,无法被轻易回收。如果你的服务器本身只有 8G 内存,且运行着高内存占用的 JVM 或 Redis,系统崩溃几乎是必然的。


二、 安全调优公式:量体裁衣

安全的做法是:根据服务器现有的空闲物理内存,倒推可以承受的最大连接跟踪数。

通常,建议用于 nf_conntrack 的内存不要超过系统总物理内存的 5% ~ 10%。我们以 5% 的保守安全边界为例,计算公式如下:

$$\text{Max_Entries} = \frac{\text{Host_RAM_Bytes} \times 0.05}{320}$$

$$\text{Hashsize} = \frac{\text{Max_Entries}}{4} \quad (\text{或 } \frac{\text{Max_Entries}}{8})$$

(注:Hashsize 是哈希表的大小。nf_conntrack 使用链地址法解决哈希冲突,通常一个哈希桶后挂 4 或 8 个结点的链表效率最高。)

实战演练:以 16GB 内存服务器为例

  1. 计算最大连接数 (Max_Entries)
    $$16 \times 1024 \times 1024 \times 1024 \times 0.05 \div 320 \approx 2,684,354$$
    为了取整,我们设定 nf_conntrack_max2,000,000 (两百万)。

  2. 计算哈希表大小 (Hashsize)
    $$\text{Hashsize} = 2,000,000 \div 4 = 500,000$$


三、 实操:不停机动态调整

调整这两个参数时,必须同时修改 maxhashsize。如果只增大 max 而不增大 hashsize,会导致哈希冲突链表过长,每次查询连接状态都会消耗极高的 CPU,引发 CPU 软中断(softirq)飙升。

1. 临时生效(即时解决故障)

由于 hashsize 属于内核模块参数,它无法通过普通的 sysctl 修改,需要通过 /sys 虚拟文件系统写入:

# 1. 先修改 hashsize (必须先改,避免桶过载)
echo 500000 > /sys/module/nf_conntrack/parameters/hashsize

# 2. 再修改 max 限制
sysctl -w net.netfilter.nf_conntrack_max=2000000

2. 永久生效(防止重启失效)

将配置分别写入系统配置文件中。

修改 /etc/sysctl.conf,加入:

net.netfilter.nf_conntrack_max = 2000000

修改 /etc/modprobe.d/nf_conntrack.conf(如果文件不存在则新建),确保模块加载时自动设置 hashsize

options nf_conntrack hashsize=500000

四、 辅助药方:缩短垃圾连接的存活期

除了单纯扩大表格容量,主动清理“占着茅坑不拉屎”的无效连接是更优雅的解法。

Linux 默认的内核参数极其保守。例如,一条已经建立的 TCP 连接(ESTABLISHED),如果没有任何数据交互,默认居然要在内核里维持 5天 (432000 秒) 的跟踪状态!这在高并发场景下简直是自杀。

将以下参数写入 /etc/sysctl.conf,大幅缩短各类状态的超时时间:

# 已经建立连接的 TCP 生命周期缩短到 30 分钟 (1800秒) 或 1 小时 (3600秒)
net.netfilter.nf_conntrack_tcp_timeout_established = 1800

# 减少等待 FIN_WAIT、CLOSE_WAIT、TIME_WAIT 等各种过渡状态的超时时间
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 120
net.netfilter.nf_conntrack_tcp_timeout_close_wait = 60
net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 120
net.netfilter.nf_conntrack_tcp_timeout_syn_sent = 120
net.netfilter.nf_conntrack_tcp_timeout_syn_recv = 60

执行 sysctl -p 立即生效。这一套组合拳下来,连接跟踪表的“新陈代谢”速度会提升数倍,老旧失效连接被快速清理,腾出空间给新连接。


五、 终极杀招:对特定流量禁用连接跟踪 (NOTRACK)

如果你在做高并发的 Nginx 反向代理、Redis 缓存服务或大量的 UDP DNS 解析压力测试,而这些服务并不需要复杂的防火墙安全过滤(不需要状态检测防火墙,也没有做 SNAT/DNAT),那么最彻底的办法是直接绕过 conntrack 模块

通过 iptables 的 raw 表,我们可以对特定的端口或 IP 设置 NOTRACK

# 比如,对流入和流出本机 80 端口的 TCP 流量,直接不进行连接跟踪
iptables -t raw -A PREROUTING -p tcp --dport 80 -j NOTRACK
iptables -t raw -A OUTPUT -p tcp --sport 80 -j NOTRACK

一旦标记了 NOTRACK,这些连接将不会占用 nf_conntrack 表的任何空间。网络吞吐量能逼近物理网卡极限,同时 CPU 和内存开销降到最低。

(注意:使用 NOTRACK 后,依赖连接状态检测的 iptables 规则(如 -m state --state ESTABLISHED)将对这部分流量失效,请根据实际业务网络拓扑谨慎评估。)

点评评价

captcha
健康