网络性能与调优

本页目录

网络调优要同时优化有意义的工作量和用户等待,而不是只把某个测速数字推高。先定义目标:大文件有效吞吐、短请求 P99、连接建立速率还是小包转发 PPS;不同目标可能需要不同批次、队列和并发策略。

指标必须附带的口径
吞吐应用 payload、IP 字节还是链路速率;单向还是双向
延迟空闲/负载状态、连接是否复用、P50/P95/P99 与错误率
PPS包长、二层开销、协议、是否经过转发/加密
CPU单核和队列分布、软中断、用户态及系统态
资源并发连接、缓冲、队列、内存压力和丢弃计数
发送路径:容量、拥塞控制和排队是不同约束 应用供给 生成、加密、批次和并发 TCP socket 缓冲、接收窗口与 cwnd qdisc / pacing 调度、整形和 AQM 驱动 / NIC 队列、分段和 DMA 路径瓶颈 竞争流、容量和深队列 接收应用 处理能力和反向 ACK cwnd 是发送许可,不是一段独立 FIFO;整条路径任一处都可能限制有效吞吐。

先确认瓶颈属于哪种限制

带宽跑不满可能是单流窗口、拥塞、应用供给、CPU、加密、存储、限速或接收处理等原因。cwnd 小并不自动证明 socket buffer 小;发送应用没有持续供给数据时,扩大窗口也不会凭空产生负载。

观察下一步验证暂不该直接得出的结论
单流慢,多流快单流窗口、CPU/队列、限速与路径哈希多流已修复所有性能问题
发送队列长期有未发送数据接收窗口、拥塞许可、pacing 与应用背压只需增加 send buffer
负载升高时 RTT 大涨队列、无线竞争、CPU 调度与接收处理一定是某台路由器 bufferbloat
某核打满,整机平均低RSS/IRQ/应用线程与 NUMACPU 不是瓶颈
接收端 ZeroWindow应用读取与缓冲释放速度发送端换拥塞算法即可解决

BDP:估计需要多少在途额度

带宽时延积 BDP = 速率 × RTT 表示以该速率持续发送时,覆盖一个往返等待所需的在途数据量。它是容量规划起点,不是对每个 socket 预分配内存的命令。

目标速率与 RTTBDP(十进制)约合二进制容量
1 Gbit/s,30 ms3.75 MB3.58 MiB
1 Gbit/s,150 ms18.75 MB17.88 MiB
10 Gbit/s,200 ms250 MB238.42 MiB

将 cwnd、接收窗口和在途数据统一为字节后,可以估计某一固定窗口的速率上界。例如可用窗口 1 MiB、RTT 100 ms,即使不丢包,理想上界也只有约 83.9 Mbit/s;慢启动、pacing、应用供给等还会进一步影响结果。ss 的 cwnd 常以段数展示,需结合 MSS,不能直接和字节单位 buffer 比较。

from fractions import Fraction as F
bdp = 1_000_000_000 * F(30, 1000) / 8
assert bdp == 3_750_000
window_limit = 1_048_576 * 8 / F(100, 1000)
assert window_limit == F(83_886_080)
queue_ms = 4_000_000 * 8 / F(100_000_000) * 1000
assert queue_ms == 320
print('BDP 3.75 MB;1 MiB 窗口上界 83.88608 Mbit/s;积压延迟 320 ms')
同样增加 4 MB:用于在途数据与排在瓶颈前的意义不同 1 Gbit/s × 30 ms RTT → BDP = 3.75 MB 足够的在途额度可以覆盖往返等待,仍需持续供给和可用容量 100 Mbit/s 出口前积压 4 MB → 排队约 320 ms 缓冲不会增加出口服务速率;持续超量输入最终仍须降速或丢弃 BDP 估计使用合适的基线 RTT;把已膨胀的 RTT 再当扩容依据会放大排队。

若使用已经包含严重排队的 RTT 来扩大 buffer,可能把排队当成“必须继续填充的管道”。应结合基线/最小 RTT 与实际竞争条件,而非永远按最大 RTT 乘带宽。

更多在途量,什么时候只是在增加排队?

默认1 Gbit/s、30ms时,3.75MB恰好覆盖基线BDP。继续增加在途预算,金色排队变长但吞吐上界不再提高;减到BDP以下,队列消失而窗口开始限制吞吐。

正在呈现知识画面
更多在途量,什么时候只是在增加排队?

达到基线BDP后,额外持续在途量在这个单瓶颈模型中形成队列,不能突破出口速率。用排队RTT再次扩大预算会追逐自己制造的等待。

Linux buffer:上限、实际分配和通告窗口不同

tcp_rmem 与 tcp_wmem 的三个值描述 min/default/max 的相关内存策略;接收自动调节还受 tcp_moderate_rcvbuf 等条件影响。应用显式设置 SO_RCVBUF/SO_SNDBUF 会改变相应自动调节行为,不能同时假定内核仍完全按自动上限增长。

# 只读取当前设置,先确认当前机器和 namespace 的实际值。
sysctl net.ipv4.tcp_rmem net.ipv4.tcp_wmem \
  net.ipv4.tcp_moderate_rcvbuf net.core.rmem_max net.core.wmem_max
ss -tinm

内核默认值可能随版本和内存变化,发行版还可能覆盖。不能以“Linux 默认接收最大约 6 MB”判断所有机器。Linux 7.2 文档明确区分自动调节上限与手动 socket 选项;tcp_wmem 的自动最大值也不覆盖 net.core.wmem_max。Linux IP Sysctl

Linux 对 SO_RCVBUF/SO_SNDBUF 参数的内部记账包含额外空间,getsockopt 常返回倍增后的数值;socket 内存占用、数据 payload 和 TCP 通告窗口不应视为同一个数字。socket(7)

把接收上限改为 64 MiB 并不表示每个连接立即占满 64 MiB;但 10,000 个活跃连接若平均实际占用 4 MiB,仅这一类内存就约 39.1 GiB,还没有算其他缓冲和元数据。调高上限应同时评估并发数、内存压力和是否真的触及原限制。

队列管理:只有控制到瓶颈才有意义

队列吸收短突发,也增加等待。100 Mbit/s 出口前的 4 MB 积压约增加 320 ms 序列化等待;无论把队列扩大多少,出口都不会因此变成 1 Gbit/s。持续输入超过输出时,需要拥塞反馈、整形或业务背压。

CoDel 观察包在队列中的驻留时间,并判断最小驻留时间是否在一个 interval 内持续高于 target,从而识别持久队列;并非任意一个包刚超过默认 5 ms 就立即丢弃。信号可以是丢弃,也可在适用条件下采用 ECN 标记。CoDel

FQ-CoDel 将流分到队列并调度,配合 CoDel 控制各队列中的持久等待。它可改善交互流与大流共存,但哈希分类、不同流数与实现条件意味着“绝对公平、永不饿死”不是通用承诺。FQ-CoDel

CAKE 将整形、队列管理、流/主机隔离和 DiffServ 等机制组合。它不是简单把 fq_codel 改个名字,也不是对所有接口必然最优。整形速率、链路层开销和实际瓶颈必须匹配;下行还需要能在适当 ingress/转发路径实施控制。tc-cake

场景为什么本机改 qdisc 可能无效
1 Gbit/s 网卡后接 100 Mbit/s 不可控设备本机发送太快,深队列仍留在下游
外部多个主机共用出口本机看不到全部竞争流量
实际上行只有 100 Mbit/s,却按 950 Mbit/s 整形没有把瓶颈移到可控位置
无线容量快速变化固定整形值不一定匹配即时容量

先用 tc -s qdisc show dev eth0 观察现有结构,再决定在哪里调度或整形。更换 root qdisc 可能替换已有业务策略,操作前应记录并理解现有配置;调优文章不应直接给所有机器套一条 replace 命令。

CUBIC、BBR 与 pacing:算法名称不是性能结论

CUBIC 根据拥塞事件和窗口增长函数调整发送许可,RFC 9438 描述的是 CUBIC,不是 BBR。BBR 基于交付速率、RTT 等建立路径模型并控制 pacing/inflight;不同代际的丢包、ECN 和探测行为不同。CUBIC RFC 9438、BBR 官方项目

BBR 的启动和带宽探测可以制造队列,也不能保证始终优于 CUBIC。应报告确切内核/补丁版本、竞争流、RTT、丢包来源和测量目标。本地名为 bbr 的模块不自动意味着已部署 Google 仓库最新代际。

sysctl net.ipv4.tcp_available_congestion_control \
  net.ipv4.tcp_congestion_control
tc -s qdisc show dev eth0

全局默认拥塞算法主要影响后续相应连接,应用也可能逐 socket 选择算法;改一次 sysctl 不会证明所有现存连接已切换。发送 pacing 与 qdisc/内核实现协作,fq、fq_codel 和 cake 不是同一机制,不能仅因名字含 fq 就认为它们的能力完全相同。tc-fq

小包:先算 PPS,再看 CPU 分布

理想 10 Gbit/s Ethernet 最小帧线速,按 64 B 帧(含 FCS)、8 B 前导码/SFD 与 12 B 帧间隙计算,每帧占 84 B 时间,约 14.88 Mpps。它与“10 Gbit/s 的大文件下载”给 CPU 的压力完全不同。

from fractions import Fraction as F
small_pps = F(10_000_000_000, (64 + 8 + 12) * 8)
large_pps = F(10_000_000_000, (1518 + 8 + 12) * 8)
assert 14_880_000 < small_pps < 14_890_000
assert 812_000 < large_pps < 813_000
assert 18 < small_pps / large_pps < 19
assert F(10_000 * 4 * 1024**2, 1024**3) == F(625, 16)
print('理想最小帧 14.88 Mpps,1518 B 帧 0.813 Mpps,平均缓冲约 39.06 GiB')

此计算忽略更高层物理编码等其他速率口径差异,且不表示当前主机能达到该 PPS。高包速下不应假定每包都触发一次独立中断;NAPI、中断合并和批处理会改变 CPU 成本。

机制主要作用取舍
TSO硬件执行 TCP 分段减少软件逐段工作,受硬件和报文条件约束
GSO在软件栈中延后分段不是把所有工作都搬到 NIC
GRO接收路径合并适用报文改变上层处理粒度和抓包观察形态
RSS硬件将流散列到接收队列单流通常仍映射到一个队列,避免乱序
RPS/RFS/XPS软件 CPU/队列选择与局部性优化可能增加跨核传递,不是核数越多越好
Ring / interrupt coalescing缓冲突发、减少中断频率更深缓冲或更多合并可增加等待

按 CPU、IRQ、队列和 NUMA 位置查看负载,通常比先把所有 ring 调到最大更有意义。卸载问题也应先对照抓包位置与驱动统计;关闭卸载会改变 CPU 与吞吐,必须作为明确假设下的对照。Linux Scaling、Segmentation Offloads

常见参数应该对应什么证据

参数或机制合理的调查入口不能替代什么
somaxconn / listen backlogaccept 队列积压、溢出与应用 accept 速度增加实际业务处理能力
tcp_max_syn_backlog未完成握手队列压力及相关计数与已完成队列混为一谈
TCP_NOTSENT_LOWAT未发送数据过多、应用可写通知和背压精确限制线上在途字节
TCP Fast Open支持 TFO 的应用、握手成本与重放语义单改 tcp_fastopen=3 就让所有应用生效
tcp_tw_reuse真实源端口/连接复用需求及协议条件TIME_WAIT 越少越好

Linux 7.2 的 tcp_tw_reuse 区分 0 禁用、1 全局启用和 2 仅 loopback,不能只给“设置 1、客户端安全”的结论。先检查连接复用、目的端分布、源端口约束和业务生命周期;删除大量 TIME_WAIT 并非优化目标。Linux IP Sysctl 参数

让测试回答一个具体问题

以下命令用于已有授权测试服务的环境;iperf3 的 server 需由测试双方事先准备。

# 客户端向服务端发送,单流。
iperf3 -c perf.example -t 30 -P 1 -J

# 反向:服务端向客户端发送,不是同时测双向。
iperf3 -c perf.example -t 30 -P 1 -R -J

# 对照多流;不能与单流结果混成一个数字。
iperf3 -c perf.example -t 30 -P 4 -J

-R 只反转方向;同时双向是另一个测试条件。iperf3 测得的是工具与端点在该条件下的吞吐,不是脱离 CPU、内核和 socket 的“裸网络带宽”。UDP 测试的发送目标速率、接收端 goodput、丢失和乱序也应分别报告。iperf3 官方手册

一次对照至少记录测试版本、方向、流数、持续时间、包长或块大小、两端 CPU、吞吐、负载下 RTT/尾延迟与错误率。为避免外部负载变化误导,可交替运行基线和候选配置。若吞吐提高 5%,但 P99 翻倍或错误率上升,结果应按原目标评价,而不是只报告最快的一次。

具体证据采集见tcpdump 与 Wireshark,阶段定位见网络故障诊断方法论。每项变更都应能对应一个已观察到的限制,并以相同工作负载验证其改善与代价。

从测量进入排队模型

若吞吐接近上限时延迟快速增长,先按排队论的测量与仿真固定请求口径、并发模型与测量窗口,再用Little 定律核对平均在途数量、吞吐与平均逗留时间。均值关系不能直接推出 p99;调参前先区分处理变慢、排队变长与客户端压测方式改变。