技术指南
网络性能诊断:基线、瓶颈与可验证回滚
用固定路径和成对样本拆分 DNS、连接、首字节、吞吐与丢包,再以单变量 canary 验证回滚。
网络性能回归不是先找一个优化参数,而是先固定用户路径、协议、负载和时间窗,再把 DNS、连接、首字节、传输、丢包、重传、CPU 与应用延迟放进同一条证据链。只有候选变更在相同样本上改善目标指标、没有伤害保护指标,并且能撤销,才值得保留。
来源列出大量工具,本文只保留可复查的回归方法
归档来源覆盖 Speedtest、iPerf3、MTR、NextTrace、磁盘测试、进程流量、BBR、sysctl、第三方内核、Smokeping 和自动通知。可保留的是固定条件、多方向测量、长期趋势、单变量修改与回退意识;线路品牌排名、固定地区延迟、80% 达标线、一次性跑分、通用大缓冲模板、全局禁用 IPv6、第三方内核安装和带密钥通知脚本均不进入本文。磁盘 I/O 与应用 profiling 也应独立验收,不能混进网络结论。
先写回归问题,而不是先选工具
| 问题 | 主指标 | 保护指标 | 固定条件 |
|---|---|---|---|
| 用户打开页面变慢 | DNS、connect、TLS、TTFB、total | HTTP 状态、内容摘要、错误率 | 同地区、URL、协议、缓存状态 |
| 单次下载变慢 | 单流吞吐、RTT、重传 | CPU、磁盘、服务端限速 | 同文件、方向、时长、时段 |
| 并发业务退化 | 业务吞吐、p95/p99 | 错误率、队列、CPU softirq | 同并发模型和数据集 |
| 某运营商间歇异常 | 端点丢包、路径与业务请求 | 其他运营商和反向样本 | 同协议、方向、目标、时间窗 |
阈值来自业务 SLO、供应商合同或自己的历史分布,不来自美西必须低于多少毫秒之类通用表格。保存原始样本和环境指纹,再计算中位数、尾部、波动与置信范围;单个最好值和自动选择的测速节点不能代表用户路径。
把环境指纹和原始样本一起保存
run_id=$(date -u +%Y%m%dT%H%M%SZ)
out=network-${run_id}
mkdir -m 0700 "${out}"
{
date -u --iso-8601=seconds
uname -a
ip -brief address
ip route show table all
sysctl net.ipv4.tcp_congestion_control
ss -s
} >"${out}/environment.txt"
ping -n -c 30 -i 0.2 TARGET_IP >"${out}/ping.txt"
mtr --report --wide --report-cycles 30 --no-dns TARGET_IP \
>"${out}/mtr-icmp.txt"- 记录 UTC、客户端和服务端位置、地址族、目标 IP、协议、方向、时长、并发与工具版本。
- 至少保留一个完整业务窗口,并在同一时段重复基线与候选,避免把昼夜波动归因于配置。
- 把失败和超时留在分母中,不只保存成功请求。
- 同一批次同时保存应用状态、响应摘要、主机 CPU/内存和接口计数。
- 开始前写停止条件,测试负载触碰错误率、带宽或 CPU 预算就终止。
按 DNS、连接、服务与传输分层定位
| 层 | 可观察证据 | 不能直接推出 |
|---|---|---|
| DNS | 解析耗时、A/AAAA、权威与递归结果 | 目标端口一定可达 |
| TCP/TLS | connect、appconnect、握手错误 | 应用处理一定健康 |
| HTTP | TTFB、状态、total、响应大小/摘要 | 链路吞吐上限 |
| 路径 | MTR 各跳响应、终点丢包、路径变化 | 不回复探测的中间路由器正在丢转发流量 |
| TCP 会话 | RTT、rttvar、cwnd、retrans、pacing | 瓶颈必然属于网络而非发送端 CPU/应用 |
用 curl 拆开连接、首字节和总耗时
for family in 4 6; do
for sample in $(seq 1 10); do
printf 'family=%s sample=%s ' "${family}" "${sample}"
curl --ipv${family} --silent --show-error --fail \
--connect-timeout 5 --max-time 20 --output /dev/null \
--write-out 'code=%{response_code} remote=%{remote_ip} dns=%{time_namelookup} connect=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total} bytes=%{size_download} speed=%{speed_download}\n' \
https://www.example.com/health
done
donecurl 当前把 time_connect 定义为到 TCP 连接完成的累计时间,把 time_appconnect 定义为 TLS 等应用连接完成时间,把 time_starttransfer 定义为首字节累计时间;TTFB 同时包含此前阶段和服务端处理。speed_download 是整次下载的平均字节/秒。对同一 URL 重复采样,才能区分解析、握手、服务端和传输阶段。
MTR 的中间跳丢包只是一条线索
target=203.0.113.20
mtr --report --wide --report-cycles 100 --no-dns "${target}"
mtr --tcp --port 443 --report --wide --report-cycles 100 \
--no-dns "${target}"
ping -n -c 100 -i 0.2 "${target}"吞吐测试要分清单流、反向与并行
# 服务端仅绑定测试网络,单次连接后退出
iperf3 --server --one-off --server-max-duration 30 \
--bind TEST_SERVER_IP --port 5201
# 客户端固定服务端、端口、时长并保存 JSON
iperf3 --client TEST_SERVER_IP --port 5201 --time 15 --json \
>single-forward.json
iperf3 --client TEST_SERVER_IP --port 5201 --time 15 --reverse --json \
>single-reverse.json
iperf3 --client TEST_SERVER_IP --port 5201 --time 15 --parallel 4 --json \
>parallel-four.jsoniPerf3 当前文档说明默认 TCP 5201 控制连接,reverse 让服务端发送,parallel 创建多流,JSON 便于保留结果。多流高于单流可能暴露单流窗口、CPU 或拥塞控制差异,也可能只是更强地占满链路;它不是用户体验更好的自动证明。测试时同时记录发送端/接收端、CPU、重传与业务影响。
把 socket 与主机资源放进同一时间窗
ss -s
ss -tin state established
nstat -az | sed -n '/TcpRetransSegs\|TcpExtTCPLoss\|IpInDiscards/p'
ip -s link show dev eth0
mpstat -P ALL 1 5ss -i 可显示 RTT、RTT 波动、拥塞算法、cwnd、重传、pacing 与已确认字节等 TCP 内部信息。若吞吐上升同时 CPU 饱和、重传增加或业务尾延迟恶化,就不能只凭 Mbps 接受候选。先排除应用限速、磁盘、虚拟化 steal、softirq 和对端瓶颈,再讨论内核参数。
常见现象要保留多个可证伪解释
| 现象 | 优先核对 | 常见误判 |
|---|---|---|
| ping 稳定但页面慢 | DNS/TLS/TTFB、应用日志、缓存 | 把服务端处理归为线路 |
| 并行快、单流慢 | RTT、cwnd、重传、CPU、对端限速 | 直接换拥塞算法 |
| 晚间才退化 | 同路径多日趋势、运营商与业务指标 | 用一次白天 Speedtest 否认 |
| 某中间跳高丢包 | 终点/后续跳、TCP 443 与真实请求 | 把 ICMP 限速当故障点 |
| IPv6 偶发慢 | A/AAAA、v4/v6 connect/TLS/route | 全局禁用 IPv6 |
变更前先证明它对应观察到的瓶颈
- 候选只改变一个控制项,并写明机制、预期改善、保护指标、停止条件和回滚命令。
- 读取当前有效值和可用选项;不要把通用 sysctl 大数值追加到主配置。
- 先在 canary 或隔离环境验证语法、启动、连通和恢复,再进入真实路径。
- 复用完全相同的目标、时段、方向、负载和样本数比较基线/候选。
- 未达到预先写定的业务阈值,或保护指标变差,就撤销而不是继续叠加参数。
IPv4 与 IPv6 分开测,不修改全局优先级猜答案
getent ahosts www.example.com
ip -4 route get 198.51.100.20
ip -6 route get 2001:db8::20
curl -4 --silent --show-error --output /dev/null \
--write-out 'v4 remote=%{remote_ip} connect=%{time_connect} total=%{time_total}\n' \
https://www.example.com/
curl -6 --silent --show-error --output /dev/null \
--write-out 'v6 remote=%{remote_ip} connect=%{time_connect} total=%{time_total}\n' \
https://www.example.com/若某地址族稳定失败,继续核对 DNS、默认路由、MTU、云网络和应用 Happy Eyeballs 行为。临时软件级强制地址族可以帮助定位,但不是永久修复;全局改 gai.conf 或禁用 IPv6 会改变所有服务的故障模式,必须单独评审。
用成对样本验收,而不是比较两个最好值
- 在同一窗口交替采集 baseline 与 candidate,避免时间趋势偏向其中一组。
- 分别报告成功率、超时、p50/p95/p99、吞吐分布和重传差值,不删除异常值。
- 真实业务 canary 与主动探测同时存在:主动测试解释链路,业务指标决定是否扩大。
- 至少覆盖两个方向和实际地址族;必要时覆盖单流与代表性并发。
- 扩大范围后重新采样,不把 canary 的 CPU、路径或邻居条件外推到全部实例。
回滚要恢复有效值,并证明性能与业务一起恢复
触发条件可以是错误率、超时、尾延迟、重传、CPU softirq、连接队列或吞吐保护线恶化。停止新测试流量,撤销候选 drop-in、qdisc、路由或应用配置,重载对应组件;再以同一探针证明有效值、连接、响应摘要和性能回到基线范围。不要用重启掩盖不知道哪个参数生效,也不要保留无归属的测试端口。
隔离演练证明了指标响应与完整回滚
VPScope 在一次性 Linux network namespace 中启用 loopback,并在 127.0.0.1:18327 启动 Python HTTP 源站。基线 20 次 ping 的平均 RTT 为 0.053 ms;仅在该 namespace 的 loopback 加入 30 ms netem 单向延迟后,平均往返 RTT 为 60.806 ms,curl 平均 connect 从 0.000215 秒升至 0.060558 秒,TTFB 从 0.001924 秒升至 0.122443 秒。
随后独立施加 25% 随机丢包,40 个往返样本观察到 32.5% 往返丢失;这不是校准后的单向损失率,只证明样本和指标会响应受控故障。删除 qdisc 后 RTT 回到 0.045 ms,connect 回到 0.000191 秒。宿主 loopback qdisc 前后完全一致,宿主不存在 18327 监听。该演练不证明公网路径、运营商、iPerf3 吞吐、BBR、NIC、云 QoS 或真实业务收益。
网络性能变更前的可复查清单
- 问题、用户路径、业务 SLO、主指标、保护指标和停止条件已经写明。
- 环境指纹、地址族、目标、协议、方向、时段、时长、并发和工具版本均随原始样本保存。
- DNS、connect、TLS、TTFB、total 与吞吐分开,失败和超时保留在分母中。
- MTR 中间跳只作为线索,并以终点、业务协议和实际请求相互印证。
- 单流、反向和并行吞吐各自有目的,没有把最高 Mbps 当成用户体验。
- socket RTT/cwnd/retrans、接口计数、CPU、应用错误率和尾延迟在同一窗口采集。
- 阈值来自业务或历史分布,没有使用固定地区延迟、线路品牌或 80% 通用标准。
- 候选只改一个控制项,已在 canary 中用成对样本比较,并监控保护指标。
- 回滚命令、有效值、配置来源和测试端口清理已经验证。
- 隔离测试的边界清楚,公网、供应商和真实用户结论仍需在实际路径复测。