我踩过的那些坑

先说个真事。去年有个客户,50 台服务器跑 API 网关,QPS 死活压在 2w 上不去,CPU 才 40%。查了半天,最后发现是 net.core.somaxconn 还是默认的 128。改了之后直接冲到 6w+。

老实说,Linux 默认内核参数是为“兼容一切”设计的,不是为“高性能”设计的。你需要自己动手把它喂饱。

⚠️ 重要提醒:下面所有配置,先在测试环境验证,再用 sysctl -p 或重启确认生效。别一股脑全贴上去,根据你的场景选。

一、快速诊断:你的 Linux 网络现在有多烂?

动手调优之前,先看看当前状态。不分析就乱调,等于闭着眼给病人开药

1.1 看丢包

# 看一眼网卡层面的丢包
ip -s link show eth0 | grep -A5 "RX:"

# 再看 TCP 层的重传和丢包
netstat -s | grep -E "segments retransmited|lost"

如果 RX dropped 一直在涨,说明网卡驱动队列不够深或者 CPU 处理不过来。如果重传率超过 1-2%,网络质量或拥塞控制可能有问题。

1.2 看连接队列

# 查看当前 TCP 连接状态分布
ss -s

# 看 SYN 队列溢出(半连接)
netstat -s | grep "SYNs to LISTEN"

SYNs to LISTEN 数字很大,说明 net.ipv4.tcp_max_syn_backlog 太小,连接请求被直接丢掉。

1.3 看软中断分布

# 看看软中断跑在哪几个核上,有没有某个核特别高
cat /proc/softirqs | head -1 && cat /proc/softirqs | grep NET_RX

如果某个 CPU 核的 NET_RX 数值是其他核的好几倍,说明中断绑得不均衡。这个问题等下在第四节解决。

1.4 看看现在的内核参数长啥样

sysctl -a | grep -E "net.core|net.ipv4.tcp|net.ipv4.ip_local"

很多默认值低得离谱——net.core.somaxconn 默认 128,net.ipv4.ip_local_port_range 起始端口 32768,能用的端口才 2 万多个。难怪高并发下扛不住。

二、核心参数调优:8 条让服务器起飞的神配置

直接上代码,创建 /etc/sysctl.d/99-network-performance.conf

# /etc/sysctl.d/99-network-performance.conf
# 适用于:高并发 Web 服务 / API 网关 / 消息队列

# 1. TCP 监听队列(重要!!!)
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535

# 2. 网卡收包队列 - 防止突发流量丢包
net.core.netdev_max_backlog = 65536

# 3. TCP 缓冲区(最小、默认、最大)
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216

# 4. 本地端口范围 - 扩大可用端口池
net.ipv4.ip_local_port_range = 1024 65535

# 5. TIME_WAIT 复用(高并发短连接神器)
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15

# 6. 开启 TCP 窗口缩放(高延迟链路必备)
net.ipv4.tcp_window_scaling = 1

# 7. SYN Cookie 防护(防攻击,也防队列满丢包)
net.ipv4.tcp_syncookies = 1

# 8. 启用 BBR(看下一节)
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr

保存后加载:

sudo sysctl -p /etc/sysctl.d/99-network-performance.conf
# 或者直接加载所有
sudo sysctl --system

来逐条解释为什么是这些值:

  • somaxconntcp_max_syn_backlog:一个管全连接队列,一个管半连接队列。高并发下默认 128 根本不够,连接请求直接丢。改到 65535 是常规操作。
  • netdev_max_backlog:内核从网卡收包后先丢进这个队列。如果你的服务偶发丢包但 CPU 又不高,十有八九是这个值太小。
  • tcp_rmem/wmem:TCP 滑动窗口的缓冲区。第三个数字是上限,我习惯设 16MB,内存够的话可以更大(比如 64MB)。有个公式可以参考:BDP = 带宽(Mbps) × RTT(ms) / 8。比如 1Gbps 链路、10ms RTT,BDP ≈ 1.25MB,所以 16MB 绰绰有余。
  • ip_local_port_range:改成 1024 到 65535,可用端口从 2.8 万变成 6.4 万。如果你的服务做大量主动出向连接(比如数据库连接池),这个很关键。
  • tcp_tw_reuse:允许复用 TIME_WAIT 状态的端口。注意是 reuse 不是 recycle——tcp_tw_recycle 在 4.12+ 内核里已经被移除了,别碰。

三、BBR 拥塞控制:高延迟网络的救星

Google 开发的 BBR 算法通过估算带宽和 RTT 来动态调速,在高延迟高丢包场景下比传统的 Cubic 强太多了。我在一个跨国业务上实测,伦敦到新加坡的链路,吞吐量从 30Mbps 直接拉到 180Mbps。

启用 BBR 需要两个条件:

  1. 内核版本 ≥ 4.9
  2. 队列规则用 fq(公平队列)
# 检查内核版本
uname -r

# 启用 BBR(永久配置已在上一节写好,这里临时验证)
echo "bbr" > /proc/sys/net/ipv4/tcp_congestion_control
echo "fq" > /proc/sys/net/core/default_qdisc

# 验证是否生效
sysctl net.ipv4.tcp_congestion_control
# 预期输出: net.ipv4.tcp_congestion_control = bbr

一个坑必须提醒你:BBR 配上 fq 调度器才有效。如果你只改 BBR 不改 qdisc,TCP 栈会用自己的高精度定时器,CPU 消耗会明显增加。

另外,如果你跑的是纯内网服务(低延迟、高带宽),BBR 不一定比 Cubic 好。内网建议老老实实用 Cubic。

四、网卡层优化:别让硬件拖后腿

软件参数改完了,别忘了网卡本身也可能是个瓶颈。

4.1 先看看网卡队列够不够

# 查看当前队列数
ethtool -l eth0

输出类似这样:

Pre-set maximums:
RX:		8
TX:		8
Current hardware settings:
RX:		4
TX:		4

如果 Current 小于 Maximum,说明队列没用满。

4.2 增加队列数

# 把 RX 和 TX 队列都设成 8(根据你的 CPU 核数来,不要超过核数)
ethtool -L eth0 combined 8

队列数一般建议等于 CPU 核数或者核数的一半。比如 16 核机器可以设 8 或 16。

4.3 增大 Ring Buffer

# 查看当前 ring 大小
ethtool -g eth0

# 增大 RX 和 TX ring(具体数字看网卡支持)
ethtool -G eth0 rx 4096 tx 4096

Ring Buffer 越大,能扛的瞬时突发流量越强,但延迟也会增加。我测试过从 256 升到 4096,丢包明显减少,但 P99 延迟从 232µs 涨到了 484µs。这有个取舍,根据自己的业务决定。

4.4 中断绑定(进阶,别偷懒)

增加队列后,还要把每个队列的中断绑到不同的 CPU 核上,才能真正发挥多核性能。

# 查看每个队列的中断号
cat /proc/interrupts | grep eth0

输出类似:

 96:  12345678  0  0  0  0  IR-PCI-MSI 327680-edge  eth0-rx-0
 97:  0  12345678  0  0  0  IR-PCI-MSI 327681-edge  eth0-rx-1
...

把中断 96 绑到 CPU 0,中断 97 绑到 CPU 1,以此类推:

echo 1 > /proc/irq/96/smp_affinity   # CPU 0 的掩码是 1
echo 2 > /proc/irq/97/smp_affinity   # CPU 1 的掩码是 2
# CPU 掩码:CPU0=1, CPU1=2, CPU2=4, CPU3=8...

(顺便提一嘴,有些发行版用 irqbalance 自动做这个事情,但它不一定聪明。高性能场景我建议手动绑。)

五、验证与监控:调完怎么知道有效?

改完参数重启服务跑压测,但怎么确认改对了?

5.1 检查参数是否真的生效

# 逐个核对关键参数
sysctl net.core.somaxconn net.core.netdev_max_backlog net.ipv4.tcp_tw_reuse

5.2 用 ss 看连接队列状态

# 查看某个端口的监听队列状态
ss -lnt 'sport = :8080'

关注 Recv-QSend-Q。如果 Recv-Q 长期接近或等于 Send-Q,说明队列快满了,还需要调大 somaxconn

5.3 用压测工具验证

# 安装 wrk
apt-get install wrk   # 或 yum install wrk

# 跑个 30 秒的压测
wrk -t12 -c400 -d30s http://你的服务地址

观察 QPS 和延迟。对比调优前后的数据。

5.4 彩蛋:用 bpftrace 实时追踪 TCP 丢包

这是真·高阶技巧。如果你的丢包问题很隐蔽,传统工具看不出来,可以上 bpftrace。

# 安装 bpftrace(Ubuntu/Debian)
apt-get install bpftrace

# 追踪内核中 TCP 丢包事件
bpftrace -e 'kprobe:tcp_drop { printf("TCP drop: %s\n", kstack()); }'

运行这个命令后,每次内核丢掉 TCP 包都会打印内核调用栈,精准定位丢包发生在哪一层。

六、常见问题与踩坑记录

Q1:改了 tcp_tw_reuse=1,端口还是不够用?

A:检查你是不是还在用 tcp_tw_recycle——这个参数在 Linux 4.12+ 内核里已经被移除了,配置了也不报错但无效。另外确认你的应用是否真的绑了 SO_REUSEADDR

Q2:改完 BBR 后 CPU 使用率反而升高了?

A:八成是忘了改 default_qdisc。检查一下:

sysctl net.core.default_qdisc

如果不是 fq,执行 sysctl -w net.core.default_qdisc=fq,然后重启服务。

Q3:ethtool -L 提示“Operation not supported”?

A:你的网卡驱动不支持动态调整队列数。试试看是不是在云虚拟机里——很多云厂商的虚拟网卡不支持这个操作。用 ethtool -i eth0 看看驱动信息。

Q4:改了参数后服务启动变慢?

A:检查 net.ipv4.tcp_max_syn_backlog 是不是设得太大了。如果内存不够,过大的 SYN 队列会占用资源。另外确认你的应用 listen 时传的 backlog 参数和内核的 somaxconn 取的是最小值,不是你设 65535 就一定能用 65535。

Q5:我的容器(Kubernetes/Docker)怎么改这些参数?

A:Docker 可以用 --sysctl 参数:

docker run --sysctl net.core.somaxconn=65535 my-image

Kubernetes 需要在 Pod 的 securityContext 里配置:

securityContext:
  sysctls:
  - name: net.core.somaxconn
    value: "65535"

注意不是所有 sysctl 参数都允许在容器里改,有些需要在宿主机层面配置。

总结与互动

核心要点回顾:

  1. 先诊断再动手——用 ip -s linknetstat -sss -s 定位瓶颈
  2. 8 个关键内核参数——somaxconnnetdev_max_backlogtcp_rmem/wmemtcp_tw_reuse 等,别再用默认值了
  3. 高延迟网络无脑上 BBR——但要配上 fq 调度器
  4. 网卡层别忽略——ethtool -L 增加队列、ethtool -G 调大 Ring Buffer、手动绑中断
  5. 验证不能省——压测 + 监控,数据说话

个人不推荐盲目抄网上的“一键优化脚本”。每个业务的流量模型不一样——API 网关和文件传输服务器需要优化的方向完全不同。先把我的配置在测试环境跑一遍,对比你的实际场景再微调。

最后抛个问题:你的线上服务有没有因为网络参数没调好出过事故?或者你遇到过什么奇怪的网络性能问题,折腾了好几天才解决?评论区聊聊,说不定你的解法能帮到更多人。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐