为什么要调优 Linux 网络栈
Linux 内核默认的网络参数是为通用场景设计的,保守而安全。但在高并发 Web 服务、大规模负载均衡器、CDN 节点等场景下,默认值会成为瓶颈。典型症状包括:
dmesg出现nf_conntrack: table full, dropping packetss -s显示大量TIME-WAIT连接- 压测时 CPU 没跑满但吞吐量上不去
- 网卡 PPS(包每秒)远低于硬件能力
这些问题大多源于内核网络参数未调优。从协议栈参数、conntrack、网卡多队列、拥塞控制四个层面详细梳理。
内核网络参数的完整说明可参考 kernel.org 文档,本文涉及的参数在
Documentation/networking/ip-sysctl.txt中有详细定义。
TCP/IP 协议栈关键内核参数
所有参数通过 sysctl 或 /proc/sys/net/ 查看和修改。以下参数均在 net.ipv4 命名空间下。
连接队列参数
# 查看当前值
sysctl net.ipv4.tcp_max_syn_backlog
sysctl net.core.somaxconn
sysctl net.core.netdev_max_backlog
somaxconn — 监听套接字的全连接队列上限:
# 默认值通常为 128 或 4096(取决于内核版本)
# 高并发场景建议设置为 65535
net.core.somaxconn = 65535
全连接队列存放已完成三次握手但尚未被 accept() 取走的连接。队列满后新连接被丢弃,客户端看到连接超时。注意:应用层面的 listen(fd, backlog) 的 backlog 值不能超过 somaxconn,实际取两者最小值。
tcp_max_syn_backlog — 半连接队列上限:
net.ipv4.tcp_max_syn_backlog = 65535
半连接队列存放已收到 SYN 但尚未完成三次握手的连接。队列满后 SYN 包被丢弃,客户端重试 SYN。SYN Flood 攻击正是通过耗尽半连接队列来拒绝服务。
netdev_max_backlog — 网卡到协议栈的接收队列上限:
net.core.netdev_max_backlog = 250000
当网卡通过中断将数据包传递给内核协议栈时,如果协议栈处理不过来,数据包会暂存在这个队列中。队列满后网卡会丢弃新包。万兆网卡或高 PPS 场景下默认值(1000)远远不够。
TIME-WAIT 相关参数
# 查看 TIME-WAIT 连接数量
ss -s | grep TIME-WAIT
# 典型输出
# TCP: 34567 (estab 8900, closed 24000, orphaned 0, synrecv 0, timewait 24000)
tcp_tw_reuse — 允许复用 TIME-WAIT 状态的连接:
net.ipv4.tcp_tw_reuse = 1
开启后,新的连接可以复用处于 TIME-WAIT 状态的本地端口。这对主动发起大量短连接的场景(如客户端、代理服务器)非常有效。注意区分:tcp_tw_reuse 用于出站连接(作为客户端),对入站连接(作为服务端)无效。
不要开启
tcp_tw_reuse的"兄弟参数"tcp_tw_recycle。 这个参数在 NAT 环境下会导致大量连接被丢弃(因为 NAT 后多台机器的时间戳不一致),已在内核 4.12 中被移除。如果你在网上看到建议同时开启两者的文章,那是过时的。
tcp_max_tw_buckets — TIME-WAIT 状态连接数上限:
net.ipv4.tcp_max_tw_buckets = 1048576
超过上限后多余的 TIME-WAIT 连接被直接销毁,并打印警告日志。这个值不是越大越好——每个 TIME-WAIT 连接占用约 1.5KB 内存,100 万个约消耗 1.5GB。
TCP keepalive 参数
# keepalive 探测前的空闲时间(秒)
net.ipv4.tcp_keepalive_time = 600
# 探测间隔(秒)
net.ipv4.tcp_keepalive_intvl = 30
# 探测失败次数,超过后断开连接
net.ipv4.tcp_keepalive_probes = 3
默认值(7200 秒 / 75 秒 / 9 次)意味着一个死连接要 2 小时才被检测到。生产环境建议缩短到 10 分钟内。
TCP 缓冲区参数
# TCP 读缓冲区(最小/默认/最大,单位字节)
net.ipv4.tcp_rmem = 4096 87380 67108864
# TCP 写缓冲区
net.ipv4.tcp_wmem = 4096 65536 67108864
# 网络层接收缓冲区
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
对于高带宽延迟积(BDP)的链路(如跨海传输),适当增大 tcp_rmem 的最大值可以提升单连接吞吐量。但注意内存消耗——每个连接的缓冲区最多可达设定最大值。
conntrack 表优化
conntrack(连接跟踪)是 Netfilter 模块的功能,记录每条网络连接的状态。负载均衡器、NAT 网关和防火墙主机上,conntrack 表满了会导致丢包。
查看状态
# 当前连接数
cat /proc/sys/net/netfilter/nf_conntrack_count
# 示例输出: 234567
# 最大连接数
cat /proc/sys/net/netfilter/nf_conntrack_max
# 示例输出: 262144
# 查看 conntrack 哈希表大小
cat /proc/sys/net/netfilter/nf_conntrack_buckets
# 示例输出: 65536
计算合理的 conntrack_max
conntrack 表大小应根据内存容量设定。每条 conntrack 记录约消耗 320 字节内存:
# 假设 64GB 内存,预留 2GB 给 conntrack
# 2GB / 320B ≈ 6,991,760
# 公式:nf_conntrack_max = 可用内存(字节) / 320
# 但实际上不会全部用于 conntrack,建议按内存的 1%-3% 估算
# 对于 64GB 内存的服务器
echo 1048576 > /proc/sys/net/netfilter/nf_conntrack_max
哈希表大小
conntrack 使用哈希表存储连接记录,哈希表大小必须是 2 的幂次方,且建议为 nf_conntrack_max 的 1/4 到 1/8:
# 哈希表大小只能在模块加载时设置,不能运行时修改
# 需要在加载 nf_conntrack 模块时传入参数
# 临时设置(需先卸载模块)
sudo modprobe -r nf_conntrack
sudo modprobe nf_conntrack hashsize=262144
# 永久设置:创建 modprobe 配置
echo "options nf_conntrack hashsize=262144" | sudo tee /etc/modprobe.d/nf_conntrack.conf
哈希表过小的危害:哈希冲突增加,查找效率从 O(1) 退化为 O(n),导致网络延迟升高。经验值:哈希表大小 >= nf_conntrack_max / 4。
conntrack 超时参数
# 查看所有超时参数
sysctl -a | grep nf_conntrack_.*_timeout
# 关键超时参数调整
# TCP 已建立连接的超时(默认 432000 秒 = 5 天,太长)
net.netfilter.nf_conntrack_tcp_timeout_established = 3600
# TIME-WAIT 状态超时(默认 120 秒)
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 30
# CLOSE_WAIT 状态超时(默认 60 秒)
net.netfilter.nf_conntrack_tcp_timeout_close_wait = 30
# SYN_SENT 状态超时(默认 120 秒)
net.netfilter.nf_conntrack_tcp_timeout_syn_sent = 30
tcp_timeout_established 默认 5 天是大坑——大量空闲连接会长期占用表项。负载均衡器上建议设置为 1 小时。
监控 conntrack 使用率
#!/bin/bash
# conntrack 监控脚本
CURRENT=$(cat /proc/sys/net/netfilter/nf_conntrack_count)
MAX=$(cat /proc/sys/net/netfilter/nf_conntrack_max)
RATIO=$(echo "scale=2; $CURRENT * 100 / $MAX" | bc)
echo "conntrack: $CURRENT / $MAX ($RATIO%)"
if (( $(echo "$RATIO > 80" | bc -l) )); then
echo "WARNING: conntrack table usage above 80%!"
fi
网卡中断与 RPS/RFS/XPS 多队列绑定
现代多核服务器的网卡通常支持多队列(multi-queue),每个队列对应一个硬件中断,可以绑定到不同的 CPU 核上处理,实现网络包的并行处理。
查看网卡队列与中断
# 查看网卡队列数量
ethtool -l eth0
# 输出示例
# Channel parameters for eth0:
# Pre-set maximums:
# RX: 0
# TX: 0
# Other: 0
# Combined: 8 # 最大支持 8 个组合队列
# Current hardware settings:
# RX: 0
# TX: 0
# Other: 0
# Combined: 8 # 当前使用 8 个队列
# 设置队列数(通常等于 CPU 核数)
sudo ethtool -L eth0 combined 8
# 查看中断号
cat /proc/interrupts | grep eth0
# 输出示例
# CPU0 CPU1 CPU2 CPU3
# 31: ... 0 0 0 0 PCI-MSI -eth0-rx-0
# 32: ... 0 0 0 0 PCI-MSI -eth0-rx-1
# 33: ... 0 0 0 0 PCI-MSI -eth0-tx-0
# 34: ... 0 0 0 0 PCI-MSI -eth0-tx-1
绑定中断到 CPU 核
#!/bin/bash
# 将 eth0 的 RX 队列中断均匀绑定到各 CPU 核
# 适用于 8 核 CPU + 8 队列网卡
IRQS=$(grep eth0-rx /proc/interrupts | awk -F: '{print $1}' | tr -d ' ')
CPU=0
MASKS=(1 2 4 8 10 20 40 80) # CPU0-CPU7 的掩码(十六进制)
for irq in $IRQS; do
# 设置中断亲和性
printf "%x" $((1 << $CPU)) > /proc/irq/$irq/smp_affinity
echo "IRQ $irq -> CPU$CPU"
CPU=$((CPU + 1))
done
注意:
smp_affinity写入的是位掩码。例如00000001表示 CPU0,00000002表示 CPU1,00000003表示 CPU0+CPU1。
RPS(Receive Packet Steering)
当网卡不支持多队列或队列数少于 CPU 核数时,RPS 在软件层面实现类似效果——将收包的软中断分散到多个 CPU 核:
# 设置 eth0 的 rx-0 队列由 CPU0-CPU7 处理(8 核掩码:ff)
echo ff > /sys/class/net/eth0/queues/rx-0/rps_cpus
# 对所有 RX 队列设置
for i in /sys/class/net/eth0/queues/rx-*/rps_cpus; do
echo ff > $i
done
# 设置 RPS 流表大小(提升流分发准确性)
echo 32768 > /sys/class/net/eth0/queues/rx-0/rps_flow_cnt
echo 32768 > /proc/sys/net/core/rps_sock_flow_entries
RFS(Receive Flow Steering)
RFS 在 RPS 基础上进一步优化:不只是把包分散到不同 CPU,而是把同一个连接的包送到处理该连接的应用程序所在的 CPU,提升 CPU 缓存命中率:
# 全局 RFS 流表大小(建议 = RPS 队列数 × 每队列 rps_flow_cnt)
echo 32768 > /proc/sys/net/core/rps_sock_flow_entries
# 每队列流表大小
for i in /sys/class/net/eth0/queues/rx-*/rps_flow_cnt; do
echo 4096 > $i
done
XPS(Transmit Packet Steering)
XPS 是发送方向的优化,指定哪些 CPU 可以通过哪些发送队列发包,减少发送路径上的锁竞争:
# 设置 tx-0 队列只能由 CPU0 使用
echo 01 > /sys/class/net/eth0/queues/tx-0/xps_cpus
# 8 队列对应 8 核
QUEUES=(0 1 2 3 4 5 6 7)
CPUS=(01 02 04 08 10 20 40 80)
for i in "${!QUEUES[@]}"; do
echo ${CPUS[$i]} > /sys/class/net/eth0/queues/tx-${QUEUES[$i]}/xps_cpus
done
RPS/RFS/XPS 效果对比
| 技术 | 方向 | 作用 | 硬件要求 |
|---|---|---|---|
| 中断绑定 | RX | 硬件中断分散到不同核 | 网卡支持多队列 |
| RPS | RX | 软中断分散到不同核 | 无(纯软件) |
| RFS | RX | 按连接亲和性分发到应用所在核 | 无(纯软件) |
| XPS | TX | 发送队列绑定到特定核,减少锁竞争 | 无(纯软件) |
TCP BBR 拥塞控制
BBR(Bottleneck Bandwidth and RTT)是 Google 开发的拥塞控制算法,2016 年进入 Linux 内核(4.9+)。与传统的 CUBIC 算法基于丢包判断拥塞不同,BBR 通过探测瓶颈带宽和往返时延来控制发送速率。
启用 BBR
# 查看当前拥塞控制算法
sysctl net.ipv4.tcp_congestion_control
# 默认输出: net.ipv4.tcp_congestion_control = cubic
# 查看内核支持的算法
sysctl net.ipv4.tcp_available_congestion_control
# 输出: net.ipv4.tcp_available_congestion_control = reno cubic bbr
# 如果没有 bbr,需要加载模块
sudo modprobe tcp_bbr
# 启用 BBR
sudo sysctl -w net.ipv4.tcp_congestion_control=bbr
# 同时设置默认 qdisc 为 fq(BBR 推荐搭配 fq 队列调度)
sudo sysctl -w net.core.default_qdisc=fq
永久生效
# 写入 /etc/sysctl.d/99-bbr.conf
sudo tee /etc/sysctl.d/99-bbr.conf << 'EOF'
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
EOF
# 加载并验证
sudo sysctl -p /etc/sysctl.d/99-bbr.conf
sysctl net.ipv4.tcp_congestion_control
# 输出: net.ipv4.tcp_congestion_control = bbr
BBR vs CUBIC 效果对比
BBR 的核心优势在长肥管道(高带宽 × 高延迟)场景下最为明显:
# 使用 iperf3 对比测试
# 服务端
iperf3 -s
# 客户端 - CUBIC
sudo sysctl -w net.ipv4.tcp_congestion_control=cubic
iperf3 -c <server_ip> -t 30 -P 4
# 客户端 - BBR
sudo sysctl -w net.ipv4.tcp_congestion_control=bbr
iperf3 -c <server_ip> -t 30 -P 4
典型结果对比(跨地域场景):
| 场景 | CUBIC 吞吐 | BBR 吞吐 | 提升 |
|---|---|---|---|
| 同机房(RTT < 1ms) | 9.8 Gbps | 9.9 Gbps | 无明显差异 |
| 跨城(RTT ~30ms) | 2.1 Gbps | 7.8 Gbps | 3.7x |
| 跨洲(RTT ~200ms) | 180 Mbps | 1.2 Gbps | 6.7x |
| 弱网(1% 丢包) | 45 Mbps | 890 Mbps | 19.8x |
BBR 在有丢包的网络环境下提升尤为显著,因为 CUBIC 一遇到丢包就大幅降速,而 BBR 不依赖丢包信号。
BBR 的注意事项
- BBR v1 vs v2:内核 5.4+ 开始支持 BBR v2(在
tcp_bbr2模块中),v2 修复了 v1 在浅队列链路上的不公平性和 bufferbloat 问题。如果内核版本支持,建议使用 v2。 - 公共云限制:部分云厂商(如 AWS)的内核不支持加载自定义 BBR 模块,需使用云厂商提供的优化内核。
- 不是万能药:BBR 优化的是单连接吞吐。对于大量短连接的场景(如 HTTP API),连接建立开销和 TLS 握手才是瓶颈,拥塞控制算法影响不大。
生产环境调优清单
以下是一份适用于高并发 Web 服务器的完整 sysctl.conf 配置,已在生产环境验证:
# /etc/sysctl.d/99-network-tuning.conf
##############################################
# 连接队列
##############################################
# 全连接队列
net.core.somaxconn = 65535
# 半连接队列
net.ipv4.tcp_max_syn_backlog = 65535
# 网卡接收队列
net.core.netdev_max_backlog = 250000
##############################################
# TCP 参数
##############################################
# 复用 TIME-WAIT 端口(仅对出站连接有效)
net.ipv4.tcp_tw_reuse = 1
# TIME-WAIT 数量上限
net.ipv4.tcp_max_tw_buckets = 1048576
# SYN+ACK 重试次数(防 SYN Flood)
net.ipv4.tcp_synack_retries = 2
# SYN 重试次数
net.ipv4.tcp_syn_retries = 2
# FIN-WAIT-2 超时时间(秒)
net.ipv4.tcp_fin_timeout = 15
# TCP keepalive
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 3
# TCP 缓冲区(最小/默认/最大)
net.ipv4.tcp_rmem = 4096 87380 67108864
net.ipv4.tcp_wmem = 4096 65536 67108864
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.core.rmem_default = 1048576
net.core.wmem_default = 1048576
# 开启 TCP Fast Open(减少一个 RTT)
net.ipv4.tcp_fastopen = 3
# 开启 MTU 探测
net.ipv4.tcp_mtu_probing = 1
# 禁用慢启动重启(长连接复用时保持窗口大小)
net.ipv4.tcp_slow_start_after_idle = 0
# 本地端口范围(出站连接可用端口)
net.ipv4.ip_local_port_range = 10000 65535
##############################################
# conntrack(负载均衡器/NAT 网关需要)
##############################################
net.netfilter.nf_conntrack_max = 1048576
net.netfilter.nf_conntrack_tcp_timeout_established = 3600
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 30
net.netfilter.nf_conntrack_tcp_timeout_close_wait = 30
net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 30
net.netfilter.nf_conntrack_tcp_timeout_syn_sent = 30
##############################################
# 拥塞控制
##############################################
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
##############################################
# 其他
##############################################
# 开启反向路径过滤(防 IP 欺骗)
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
# 禁止 ICMP 重定向
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
# 禁止源路由
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
# 应用配置
sudo sysctl -p /etc/sysctl.d/99-network-tuning.conf
# 验证关键参数
sysctl net.core.somaxconn net.ipv4.tcp_tw_reuse \
net.ipv4.tcp_congestion_control net.netfilter.nf_conntrack_max
调优验证清单
| 检查项 | 命令 | 预期 |
|---|---|---|
| 全连接队列溢出 | netstat -s | grep overflowed | 增量为 0 |
| 半连接队列溢出 | netstat -s | grep "SYNs to LISTEN" | 增量为 0 |
| conntrack 满丢包 | dmesg | grep "table full" | 无输出 |
| TIME-WAIT 过多 | ss -s | grep timewait | < conntrack_max |
| BBR 已启用 | sysctl tcp_congestion_control | bbr |
| 端口耗尽 | ss -s | grep TCP | estab < port_range |
小结
Linux 网络栈调优是系统工程,涉及从内核协议栈到网卡硬件的多个层次。核心原则是:
- 先监控再调优:不要盲目改参数,先用
netstat -s、ss -s、nstat和 conntrack 监控定位瓶颈。 - 分层次优化:连接队列 → conntrack → 网卡多队列 → 拥塞控制,按影响从大到小逐层调。
- 理解原理而非背参数:知道每个参数控制什么,才能在不同场景下做正确的取舍。
- 测试验证:每次只改一组参数,用
iperf3、wrk或真实流量压测验证效果。
延伸阅读:Linux 网络栈性能优化指南(kernel.org) 涵盖了更底层的参数定义和原理说明,适合需要深入理解内核网络子系统的读者。
参考资料与致谢
本文在撰写过程中参考了以下资料,感谢原作者的贡献:
- kernel.org 文档 — Linux 内核组织,参考了kernel.org 文档相关内容