一次 Linux 内核参数调优误配导致 TCP 性能断崖的排查记录

服务器上调 tcp_tw_reuse 与 netdev_max_backlog 后吞吐量骤降,tcpdump 定位到 PAWS 误判与 qdisc 拥塞丢包双重根因,最终靠精准回退 + sysctl 模板收口。

问题背景

生产环境一台对外提供大文件下载与 API 聚合的服务器,在高峰期偶发吞吐量断崖式下跌(从 1.2 Gbps 跌到 40 Mbps),但 iostatsartop 均未见磁盘/CPU/内存瓶颈。早期通过粗暴调优 tcp_tw_reuse=1netdev_max_backlog=30000 试图缓解 TIME_WAIT 与软中断队列压力,调优后两天内性能反而更差。最终决定系统性回溯内核参数变更,定位到 PAWS 时间戳误判与 qdisc 队列积压两个隐藏冲突。

故障现象

  • 晚高峰 20:00-22:00 对外下载速度从 120 MB/s 跌至不足 5 MB/s,P99 延迟从 80 ms 飙到 8 s。
  • ss -s 显示 TimeWait 仍维持在 3 万左右,但 Recv-QSend-Q 几乎为 0,连接看似空闲却无法推进。
  • tcpdump -i any port 80 抓包发现大量 TCP ACK 被静默丢弃,远端不断重传;同时发现部分 SYN 报文携带时间戳,但服务端回复 RST。
  • tc -s qdisc show dev eth0 显示 backlog 28765p 持续堆积,dropped 计数每秒递增数百。
  • 重启 nginx / 重载配置无效,必须重启整机才能短暂恢复。

排查过程

1. 排除应用层

先用 strace -p <nginx worker> 确认 worker 线程并未阻塞在 sendfile/writev,排除了业务代码 bug。接着用 perf top -e cpu-clock 采样 30 秒,热点落在 tcp_write_xmit__qdisc_run,指向内核网络栈。

2. 内核参数回溯

对比 /etc/sysctl.d/*.confsysctl -a 差异,发现最近三周内修改过 17 个 TCP 相关参数,其中 tcp_tw_reuse=1netdev_max_backlog=30000tcp_timestamps=1 三者同时存在。

3. PAWS 时间戳误判

tcpdump 抓到远端 SYN 携带时间戳 TSval=0x12ab,服务端回复 RST。查阅内核源码 tcp_v4_conn_request 中的 tcp_paws_check,当 tcp_tw_reuse=1 且 TIME_WAIT 桶被快速复用时,旧连接残留的时间戳可能触发 PAWS(Protect Against Wrapped Sequence numbers)误判,导致新 SYN 被丢弃。

4. qdisc 队列暴涨

netdev_max_backlog=30000 配合 fq qdisc 默认 limit 10000 不匹配,导致软中断上下文把报文塞进 qdisc 队列,队列满后直接 drop。用 tc qdisc replace dev eth0 root fq limit 40000 flow_limit 2000 临时验证,dropped 计数立即归零,吞吐量回升至 800 Mbps。

5. 复现与确认

在测试环境用 tc qdisc add dev eth0 root netem delay 50ms 模拟广域网 + 大流量压测,确认 tcp_tw_reuse + tcp_timestamps + fq limit 不匹配 三者叠加即可稳定复现 PAWS 丢包 + qdisc drop。

解决方案

  1. 紧急止血:临时把 netdev_max_backlog 回退到 5000,并把 fq limit 调大到 40000,5 分钟内恢复 90% 吞吐量。
  2. 根治参数模板
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    # /etc/sysctl.d/99-tcp-performance.conf
    net.core.netdev_max_backlog = 16384
    net.core.somaxconn = 65535
    net.ipv4.tcp_tw_reuse = 1
    net.ipv4.tcp_timestamps = 1
    net.ipv4.tcp_rmem = 4096 87380 6291456
    net.ipv4.tcp_wmem = 4096 16384 4194304
    net.ipv4.tcp_congestion_control = bbr
    net.core.default_qdisc = fq
    
  3. qdisc 持久化:用 systemd-networkd 的 .network 文件或 NetworkManager dispatcher 脚本在 post-up 阶段执行 tc qdisc replace,避免重启后配置丢失。
  4. 监控增强:在 Prometheus node_exporter 添加 node_netstat_Tcp_RetransSegstc -j qdisc show 解析脚本,每分钟告警 backlog > 10000 或 drop rate > 10 pps。

根因分析

  • tcp_tw_reuse=1 必须搭配 tcp_timestamps=1 使用,但 PAWS 检查在高并发短连接场景下会误判旧时间戳,导致 SYN 被 RST。
  • netdev_max_backlog 只控制软中断入队上限,不控制 qdisc 队列长度,两者不匹配造成入队即丢包的「静默黑洞」。
  • 调优时缺少「变更后 48 小时全链路压测 + 关键指标基线对比」,把局部优化误判为全局最优。

预防措施

  1. 参数变更 Checklist
    • 变更前必须用 sysctl -a | grep <key> 记录基线。
    • 变更后 30 分钟内跑 wrk/iperf3 压测 5 分钟,采集 ss -stc -s qdiscnetstat -s
    • 任何涉及 tcp_tw_reuse / timestamps / backlog 的变更,必须附上「PAWS 风险评估」。
  2. 模板化:把经过验证的参数集合做成 Ansible role 或 Salt state,所有新服务器必须套用。
  3. 自动化巡检:每周跑一次 ss -tan state time-wait | wc -l + tc -s qdisc 对比历史基线,偏差 > 30% 即告警。
  4. 文档闭环:在运维 Wiki 新增「Linux TCP 调优避坑指南」,把本次 PAWS + qdisc 案例写入「已踩过的坑」章节。

总结

一次看似「常规」的内核参数调优,因为缺少对 PAWS 时间戳语义与 qdisc 队列长度匹配的理解,反而引入了更严重的性能黑洞。通过系统性回溯、抓包定位、参数模板收口,最终把故障转化为可复用的 Checklist。本次事件再次印证:内核参数没有银弹,任何调优都必须经过「基线 → 变更 → 压测 → 监控」四步闭环。后续将把该 Checklist 固化进服务器上线流程,避免同类问题再次发生。

使用 Hugo 构建
主题 StackJimmy 设计