问题背景
生产环境一台对外提供大文件下载与 API 聚合的服务器,在高峰期偶发吞吐量断崖式下跌(从 1.2 Gbps 跌到 40 Mbps),但 iostat、sar、top 均未见磁盘/CPU/内存瓶颈。早期通过粗暴调优 tcp_tw_reuse=1 与 netdev_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-Q与Send-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/*.conf 与 sysctl -a 差异,发现最近三周内修改过 17 个 TCP 相关参数,其中 tcp_tw_reuse=1、netdev_max_backlog=30000、tcp_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。
解决方案
- 紧急止血:临时把
netdev_max_backlog回退到 5000,并把 fq limit 调大到 40000,5 分钟内恢复 90% 吞吐量。 - 根治参数模板:
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 - qdisc 持久化:用 systemd-networkd 的
.network文件或 NetworkManager dispatcher 脚本在post-up阶段执行tc qdisc replace,避免重启后配置丢失。 - 监控增强:在 Prometheus node_exporter 添加
node_netstat_Tcp_RetransSegs、tc -j qdisc show解析脚本,每分钟告警 backlog > 10000 或 drop rate > 10 pps。
根因分析
tcp_tw_reuse=1必须搭配tcp_timestamps=1使用,但 PAWS 检查在高并发短连接场景下会误判旧时间戳,导致 SYN 被 RST。netdev_max_backlog只控制软中断入队上限,不控制 qdisc 队列长度,两者不匹配造成入队即丢包的「静默黑洞」。- 调优时缺少「变更后 48 小时全链路压测 + 关键指标基线对比」,把局部优化误判为全局最优。
预防措施
- 参数变更 Checklist:
- 变更前必须用
sysctl -a | grep <key>记录基线。 - 变更后 30 分钟内跑
wrk/iperf3压测 5 分钟,采集ss -s、tc -s qdisc、netstat -s。 - 任何涉及
tcp_tw_reuse/timestamps/backlog的变更,必须附上「PAWS 风险评估」。
- 变更前必须用
- 模板化:把经过验证的参数集合做成 Ansible role 或 Salt state,所有新服务器必须套用。
- 自动化巡检:每周跑一次
ss -tan state time-wait | wc -l+tc -s qdisc对比历史基线,偏差 > 30% 即告警。 - 文档闭环:在运维 Wiki 新增「Linux TCP 调优避坑指南」,把本次 PAWS + qdisc 案例写入「已踩过的坑」章节。
总结
一次看似「常规」的内核参数调优,因为缺少对 PAWS 时间戳语义与 qdisc 队列长度匹配的理解,反而引入了更严重的性能黑洞。通过系统性回溯、抓包定位、参数模板收口,最终把故障转化为可复用的 Checklist。本次事件再次印证:内核参数没有银弹,任何调优都必须经过「基线 → 变更 → 压测 → 监控」四步闭环。后续将把该 Checklist 固化进服务器上线流程,避免同类问题再次发生。