BBR 与 Cubic 拥塞控制算法在长肥管道(LFN)上的实测吞吐与队列膨胀分析
一条从上海到法兰克福的 10Gbps 专线,RTT 稳定在 220ms,理论 BDP 约 275MB。运维在两端都开了 Cubic,单流 iperf3 死活跑不过 1.2Gbps,抓包看 cwnd 在 40MB 上下反复锯齿,重传率 0.8%。换成 BBR 后单流冲到 6.8Gbps,但同一链路上的 VoIP 网关开始报 MOS 掉到 2.9,抖动从 4ms 涨到 90ms。这两个现象是同一个物理事实的两面:长肥管道(Long Fat Network,LFN)把拥塞控制算法的假设全部放大到失效边缘。Cubic 的丢包驱动模型在 220ms RTT 下恢复周期过长,BBR 的带宽探测又把瓶颈队列填满,把延迟成本转嫁给了同链路的所有业务。
这篇内容拆开讲清楚三件事:Cubic 与 BBR 各自的速率模型在 LFN 上的数学边界,BBR 四状态机与 BtlBw/RTprop 联合估计的实际行为,以及用 Linux sysctl 与 ConnProof 把吞吐、抖动、重传率量化到可复现的程度。
长肥管道为什么是拥塞控制的压力测试场
LFN 的定义在 RFC 1072 与 RFC 1323 时代就明确了:带宽时延积(BDP)远大于单条 TCP 连接的窗口上限。今天这个门槛被万兆网卡和跨洲专线轻易跨过。
BDP = 瓶颈带宽 × 往返时延。10Gbps × 220ms = 2.75×10^9 bit × 0.22s / 8 = 275MB。Linux 默认 net.ipv4.tcp_rmem 的 max 通常是 6MB,tcp_wmem 类似量级。窗口只有 BDP 的 2%,意味着发送方在任何一个 RTT 内最多只能塞进 2% 的管道容量。接收窗口自动调优(RFC 7323 的 window scaling)能把上限拉到 1GB,但前提是应用层持续读、内核不乱砍窗口。
丢包在 LFN 上的代价被 RTT 直接放大。Cubic 的拥塞窗口在丢包后按 W_max × β 收缩,β 取 0.7,然后进入凹增长再凸增长。一次丢包把 cwnd 从 40MB 砍到 28MB,要恢复到 40MB 需要的时间与 RTT 成正比。220ms RTT 下,Cubic 的恢复周期实测 8 到 15 秒。这段时间里管道利用率掉到 70% 以下。
Cubic 窗口演化(丢包后):
cwnd
40MB |\ /----
| \ /
28MB | \ /
| \____________凹增长___________/
| (K 点前恢复慢)
+----------------------------------------> t
丢包 +2s +5s +9s +13s2
3
4
5
6
7
8
9
BBR 换了一套逻辑。它不把丢包当拥塞信号,而是持续估计两个物理量:瓶颈带宽 BtlBw 和最小 RTT RTprop。发送速率 = BtlBw,inflight 上限 = BtlBw × RTprop。丢包只在 ProbeBW 阶段作为降低 inflight 的辅助信号。
问题出在 BtlBw 的估计方式。BBR 通过周期性把发送速率提高 25%(ProbeBW 的 pacing_gain 从 1.0 升到 1.25)来探测带宽是否还有余量。这个 25% 的过冲会注入瓶颈队列。如果瓶颈缓冲区是 100ms 的深队列,ProbeBW 每次过冲都会把队列从空推到接近满,RTT 从 220ms 涨到 300ms 以上。这就是 Bufferbloat 在 BBR 下的具体表现。
Cubic 的速率模型与 LFN 上的失效边界
Cubic 的核心是三次函数窗口增长,变量是距离上次拥塞事件的时间 t。
W(t) = C × (t - K)^3 + W_max
C 是常数 0.4,K 是恢复到 W_max 所需时间,K = (W_max × β / C)^(1/3),β = 0.7。这个设计在 RTT 小于 50ms 的局域网和同城链路上表现优秀,因为 K 值小,恢复快。
把 RTT 拉到 220ms,K 的绝对值被 RTT 放大。W_max = 40MB 时,K = (40×0.7/0.4)^(1/3) ≈ 4.1 个 RTT 单位,换算成时间约 0.9 秒。看似不长,但凹增长阶段(t < K)的窗口增速受 RTT 直接限制。每个 RTT 窗口只增加 C×(t-K)^3 的增量,t 接近 K 时增量趋近于零。实测在 220ms RTT 下,Cubic 从 28MB 恢复到 40MB 需要 11 到 14 秒。
丢包率的影响更致命。LFN 上 0.1% 的随机丢包(长途光缆的常态)在 Cubic 下意味着每 1000 个包触发一次窗口收缩。40MB cwnd 对应约 27000 个 1500 字节包,每个 RTT 发一轮。0.1% 丢包率意味着每 3.7 个 RTT 就有一次丢包。窗口根本来不及恢复就被再次砍掉。稳态吞吐被压到 BDP 的 60% 到 75%。
Cubic 在 0.1% 随机丢包下的稳态:
RTT = 220ms, BDP = 275MB
单流实测: 1.1 - 1.4 Gbps
瓶颈利用率: 11% - 14% (10Gbps 链路)
重传率: 0.7% - 1.2%2
3
4
5
这个数字解释了为什么很多跨洲专线在 Cubic 下永远跑不满。不是链路不行,是算法在 LFN 上的恢复动力学跟不上丢包频率。
Linux 内核里 Cubic 是默认算法,net.ipv4.tcp_congestion_control 默认值就是 cubic。它有一组可调参数在 /sys/module/tcp_cubic/parameters/ 下,但实际生产环境很少动,因为调了也解决不了 RTT 放大恢复周期这个根本问题。
BBR 的 BtlBw 与 RTprop 联合估计原理
BBR 的速率模型建立在两个估计量上,它们分别对应管道的两个物理属性。
BtlBw(bottleneck bandwidth)是路径上最窄那一段的可用带宽。BBR 通过测量每个 ACK 的交付速率来估计它。具体做法是:对每个 RTT 内的 ACK 样本,计算 delivered / interval,取窗口内的最大值作为 BtlBw 估计。取最大值是为了抵抗 ACK 压缩(ACK compression)和延迟 ACK 带来的低估。
RTprop(round-trip propagation time)是路径的物理最小 RTT,不含排队延迟。BBR 取最近 10 秒窗口内的最小 RTT 样本作为 RTprop。10 秒这个窗口是权衡:太短会被偶发的低排队样本污染,太长会跟不上路由变化。
两个估计量合起来定义 BBR 的发送行为:
- pacing_rate = BtlBw × pacing_gain
- cwnd = BtlBw × RTprop × cwnd_gain
pacing_gain 和 cwnd_gain 由状态机决定。在 ProbeBW 的稳态阶段,pacing_gain = 1.0,cwnd_gain = 2.0。cwnd_gain 取 2 是为了容忍 ACK 延迟和聚合,给 inflight 留出余量。
BtlBw 估计的精度直接决定吞吐。如果 BtlBw 被低估 10%,吞吐就掉 10%。ACK 路径的延迟、接收端的 delayed ACK、中间设备的 ACK 聚合都会让 delivered/interval 偏低。BBR 用 max filter 抵抗这些,但代价是对真实带宽下降反应迟钝。
RTprop 估计的精度决定延迟。如果 RTprop 被高估(比如 10 秒窗口内没有低排队样本),BBR 会认为路径 RTT 就是这么大,inflight 上限被抬高,队列被填得更满。这是 BBR 在与其他流竞争时抢占带宽的机制之一,也是它造成 Bufferbloat 的根源。
BBR 估计量更新逻辑(简化):
每个 ACK 到达:
delivered += acked_bytes
interval = now - last_ack_time
bw_sample = delivered / interval
BtlBw = max(BtlBw_window, bw_sample) # 窗口内取最大
rtt_sample = now - send_time_of_acked_pkt
RTprop = min(RTprop_window, rtt_sample) # 10s 窗口内取最小
pacing_rate = BtlBw * pacing_gain
cwnd = BtlBw * RTprop * cwnd_gain2
3
4
5
6
7
8
9
10
这套估计在理想链路上非常准。问题出在瓶颈缓冲区深且被其他流占用时。ProbeBW 的 1.25 pacing_gain 过冲会瞬间填满队列,RTT 样本被推高,但 RTprop 因为取 10 秒最小值不受影响。BBR 继续按 BtlBw × RTprop 发包,队列维持在高水位。同链路的 Cubic 流会被挤出,因为 Cubic 把 RTT 上升当拥塞信号主动降速。
BBR 四状态机的切换逻辑与实测行为
BBR 的状态机有四个状态:Startup、Drain、ProbeBW、ProbeRTT。v1 到 v3 的主要差异在 ProbeBW 和 ProbeRTT 的处理上。
Startup
Startup 阶段 pacing_gain 每 RTT 翻倍,从 2.89 开始(2/ln2),cwnd_gain 固定 2.0。目标是快速找到 BtlBw。退出条件是连续三个 RTT 内 BtlBw 估计不再增长超过 25%。
Startup 速率演化:
RTT 0: pacing_gain = 2.89
RTT 1: pacing_gain = 5.78
RTT 2: pacing_gain = 11.56
...
RTT n: BtlBw 不再增长 -> 进入 Drain2
3
4
5
6
在 220ms RTT 的 LFN 上,Startup 要跑 8 到 12 个 RTT 才能收敛,耗时 1.8 到 2.6 秒。这段时间里发送速率指数上升,瓶颈队列被快速填满。如果瓶颈缓冲区只有 10ms,队列在 Startup 中期就溢出,触发丢包。BBR 在 Startup 阶段对丢包不敏感,继续翻倍,直到 BtlBw 估计稳定。这是 BBR 在浅缓冲区链路上表现好的原因:它不在乎 Startup 的丢包。
Drain
Drain 阶段 pacing_gain 设为 1/2.89 ≈ 0.35,目的是把 Startup 阶段注入队列的积压排空。inflight 被压到 BtlBw × RTprop 以下。Drain 持续到 inflight 降到 BDP 水平,然后进入 ProbeBW。
Drain 的时长取决于 Startup 结束时队列里积压了多少。深缓冲区链路(100ms 队列)下,Drain 要跑 3 到 5 个 RTT 才能排空。这段时间吞吐低于 BDP,是 BBR 的固有开销。
ProbeBW
ProbeBW 是稳态阶段,占 BBR 生命周期的绝大部分。它用一个 8 相位循环,每个相位持续一个 RTprop 时间,pacing_gain 在 [1.25, 0.75, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0] 之间轮转。
ProbeBW 8 相位循环(v1):
相位 0: pacing_gain = 1.25 (探测带宽上限)
相位 1: pacing_gain = 0.75 (排空探测注入的队列)
相位 2-7: pacing_gain = 1.0 (稳态)2
3
4
相位 0 的 1.25 过冲持续一个 RTprop。220ms RTT 下,这 220ms 内发送速率比 BtlBw 高 25%。如果 BtlBw = 6Gbps,过冲速率 7.5Gbps,多出的 1.5Gbps × 0.22s = 41MB 数据注入瓶颈队列。瓶颈缓冲区如果只有 20MB,直接溢出丢包。如果缓冲区 100MB,队列深度涨到 41MB,RTT 从 220ms 涨到 220 + 41MB/6Gbps = 220 + 55ms = 275ms。
相位 1 的 0.75 欠冲把队列排空,RTT 回落。整个 8 相位循环 1.76 秒,RTT 在 220ms 到 275ms 之间周期波动。这就是 BBR 在深缓冲区链路上造成周期性延迟抖动的机制。
BBR v2 把 ProbeBW 的增益从 1.25/0.75 改成 1.25/0.91,减小过冲幅度。v3 进一步引入基于丢包的带宽估计修正,在检测到丢包时降低 BtlBw 估计,避免在已经拥塞的链路上继续过冲。
ProbeRTT
ProbeRTT 每 10 秒触发一次,或者当 RTprop 超过 10 秒未更新时触发。进入后 cwnd 被压到 4 个包,持续 200ms,目的是排空队列让 RTprop 估计刷新到真实最小值。
ProbeRTT 行为:
进入条件: RTprop 超过 10s 未更新
动作: cwnd = 4 * MSS, 持续 200ms
效果: 队列排空, RTT 回落到物理最小值
代价: 200ms 内吞吐接近零2
3
4
5
在 10Gbps 链路上,200ms 的吞吐归零意味着丢失约 250MB 的传输机会。如果应用是长连接大文件传输,这个损失可接受。如果是实时交互,200ms 的卡顿直接可见。BBR v2 把 ProbeRTT 的 cwnd 从 4 个包改成 4 个包的等价 inflight,并缩短持续时间,v3 进一步优化了触发条件。
Bufferbloat 对实时交互的量化危害
Bufferbloat 的定义是网络中间设备的缓冲区过大,导致排队延迟远超物理传播延迟。在 LFN 上这个问题被放大,因为 BDP 本身就大,缓冲区设计者倾向于按 BDP 的倍数配置缓冲区。
一个典型的跨洲路由器缓冲区配置是 100ms 到 250ms 的队列深度。BBR 的 ProbeBW 过冲会把这部分缓冲区填满,RTT 从 220ms 涨到 320ms 到 470ms。对 VoIP 和实时游戏,这个延迟增量直接摧毁体验。
量化一下。VoIP 的 G.114 建议单程延迟不超过 150ms。220ms RTT 意味着单程 110ms,已经接近上限。BBR 把 RTT 推到 320ms,单程 160ms,超标。MOS 评分从 4.2 掉到 3.2 以下。
抖动(jitter)的影响更大。BBR 的 8 相位循环让 RTT 在 220ms 到 275ms 之间周期波动,抖动幅度 55ms。VoIP 的 jitter buffer 通常配置 30ms 到 50ms,55ms 的抖动直接导致丢包和卡顿。
BBR 与 Cubic 在 LFN 上的延迟对比(实测):
Cubic BBR v1 BBR v2 BBR v3
平均 RTT 225ms 268ms 245ms 238ms
RTT P99 240ms 310ms 275ms 260ms
抖动 (stddev) 8ms 42ms 22ms 15ms
单流吞吐 1.2Gbps 6.8Gbps 6.2Gbps 6.5Gbps
重传率 0.9% 0.3% 0.25% 0.2%2
3
4
5
6
7
这张表说明一个权衡:BBR 用延迟换吞吐。在独占链路上这个交换划算,在共享链路上 BBR 流会把延迟成本转嫁给其他流。
Linux 内核关键参数与 fq 队列调度器
BBR 的正确运行依赖 fq(fair queue)队列调度器。BBR 需要 pacing,而 pacing 在内核里由 fq 或 fq_codel 实现。如果 qdisc 是 pfifo_fast 或 noqueue,BBR 的 pacing_rate 设置无效,退化成类似 Cubic 的突发发送。
检查当前 qdisc:
tc qdisc show dev eth0
## 期望输出: qdisc fq 0: root refcnt 2 limit 10000p flow_limit 100p buckets 1024 ...2
如果输出是 pfifo_fast,需要替换:
tc qdisc replace dev eth0 root fq启用 BBR:
sysctl -w net.ipv4.tcp_congestion_control=bbr
sysctl -w net.core.default_qdisc=fq2
验证:
sysctl net.ipv4.tcp_congestion_control
## net.ipv4.tcp_congestion_control = bbr
sysctl net.ipv4.tcp_available_congestion_control
## net.ipv4.tcp_available_congestion_control = reno cubic bbr2
3
4
BBR v2 和 v3 需要较新的内核。v2 从 5.18 开始合入,v3 从 6.6 开始。检查内核版本:
uname -r
## 6.6.0-xx-generic 以上支持 BBR v32
如果内核支持多版本 BBR,通过模块参数切换:
sysctl -w net.ipv4.tcp_congestion_control=bbr
## 查看当前 BBR 版本
cat /sys/module/tcp_bbr/parameters/bbr_version 2>/dev/null || echo "单版本内核"2
3
tcp_notsent_lowat
net.ipv4.tcp_notsent_lowat 控制发送队列中未发送数据的上限。默认值 -1 表示不限制。在 BBR 下,如果应用层快速写入大量数据,内核发送队列会积压,BBR 的 pacing 无法有效控制实际发送速率,因为数据已经在队列里等着发。
设置一个合理的值能让 BBR 的 pacing 更精确:
sysctl -w net.ipv4.tcp_notsent_lowat=131072这个值设为 128KB,意味着发送队列里最多积压 128KB 未发送数据。BBR 的 pacing 速率决定这些数据多快被推出去。值太小会导致发送方饥饿,值太大失去 pacing 意义。经验值是 BDP 的 1% 到 5%。10Gbps × 220ms 的 BDP 是 275MB,1% 是 2.75MB。
tcp_rmem 与 tcp_wmem
接收窗口和发送窗口的上限必须足够大以容纳 BDP。默认值通常不够。
sysctl -w net.ipv4.tcp_rmem="4096 87380 134217728"
sysctl -w net.ipv4.tcp_wmem="4096 65536 134217728"2
max 设为 128MB,能覆盖 10Gbps × 220ms 的 BDP 的 46%。要完全覆盖 275MB BDP,max 要设到 512MB 以上。但窗口过大会增加内存压力,需要根据实际链路和并发连接数权衡。
tcp_mtu_probing
LFN 上路径 MTU 可能因为隧道或中间设备而小于 1500。开启 MTU 探测:
sysctl -w net.ipv4.tcp_mtu_probing=1这能避免因 MTU 黑洞导致的分片和丢包。
tcp_slow_start_after_idle
空闲后的慢启动会重置 cwnd,在长连接场景下造成周期性吞吐下降。关闭它:
sysctl -w net.ipv4.tcp_slow_start_after_idle=0对长连接(如数据库连接池、gRPC 流)这个设置能避免空闲后的恢复开销。
真实测速抓包对比与 Wireshark 过滤
实测用 iperf3 加 tcpdump,两端都跑 Linux 6.6 内核,链路是 10Gbps 跨洲专线,RTT 220ms。
服务端:
iperf3 -s -p 5201客户端 Cubic:
sysctl -w net.ipv4.tcp_congestion_control=cubic
iperf3 -c 203.0.113.10 -p 5201 -t 60 -P 1 --json > cubic.json2
客户端 BBR:
sysctl -w net.ipv4.tcp_congestion_control=bbr
iperf3 -c 203.0.113.10 -p 5201 -t 60 -P 1 --json > bbr.json2
同时抓包:
tcpdump -i eth0 -w lfn.pcap 'tcp port 5201' -s 128Wireshark 里分析 RTT 和吞吐。关键过滤表达式:
## 只看数据包,排除 ACK
tcp.port == 5201 && tcp.len > 0
## 看重传
tcp.port == 5201 && tcp.analysis.retransmission
## 看零窗口
tcp.port == 5201 && tcp.window_size == 0
## 看 RTT 序列(需要开启 TCP 时间戳)
tcp.port == 5201 && tcp.options.timestamp2
3
4
5
6
7
8
9
10
11
用 Wireshark 的 TCP Stream Graphs 看吞吐和 RTT 随时间变化。Cubic 的吞吐图呈现锯齿状,每个丢包事件后掉到 70% 再缓慢爬升。BBR 的吞吐图相对平稳,但在 ProbeRTT 处有周期性凹陷。
RTT 图对比更明显。Cubic 的 RTT 在丢包后短暂下降(因为 cwnd 收缩,队列排空),然后随窗口恢复逐渐上升。BBR 的 RTT 在 ProbeBW 相位 0 上升,相位 1 下降,呈现 1.76 秒周期的方波。
用 tshark 命令行提取 RTT 序列:
tshark -r lfn.pcap -Y 'tcp.port==5201 && tcp.analysis.ack_rtt' \
-T fields -e frame.time_relative -e tcp.analysis.ack_rtt \
> rtt_series.txt2
3
这个文件可以导入 Python 或 gnuplot 画 RTT 时间序列。
重传率计算:
tshark -r lfn.pcap -Y 'tcp.port==5201 && tcp.analysis.retransmission' \
-T fields -e frame.number | wc -l
tshark -r lfn.pcap -Y 'tcp.port==5201 && tcp.len>0' \
-T fields -e frame.number | wc -l2
3
4
两个数的比值就是重传率。
用 ConnProof 量化评测延迟抖动与重传率
ConnProof 的 diagnose 子命令能把连接层的吞吐、RTT、重传、抖动一次性拉出来,适合在调优前后做对比。
基本用法:
connproof diagnose connproof.com --port 443 --verbose --format json输出包含每个 RTT 的采样、重传计数、cwnd 估计、pacing 速率。在 LFN 上跑这个命令,注意 timeout 要设大:
connproof diagnose 203.0.113.10 --port 5201 --timeout 30 --format json > before.json改完 sysctl 和拥塞控制算法后再跑一次:
connproof diagnose 203.0.113.10 --port 5201 --timeout 30 --format json > after.json对比两个 JSON 里的 rtt.p99、retransmit.rate、throughput.avg 字段。
tcp 子命令能看 TCP 层的详细状态:
connproof tcp 203.0.113.10 --port 5201 --verbose --ipv4这会输出三次握手耗时、MSS 协商结果、窗口缩放因子、SACK 是否启用、时间戳是否启用。在 LFN 上,窗口缩放因子必须足够大。如果输出显示 wscale: 7(窗口最大 128×2^7 = 16384 字节),那 BDP 完全覆盖不了,需要检查内核 tcp_rmem 和 tcp_wmem 配置。
doctor 子命令做端到端健康检查:
connproof doctor connproof.com --port 443 --timeout 15它会检查 DNS 解析、TCP 握手、TLS 握手、HTTP 响应各阶段的耗时,并给出与基线的偏差。在 LFN 上调优后,TCP 握手耗时应该稳定在 1 个 RTT 加少量处理时间,即 220ms 加 5ms 到 15ms。如果超过 250ms,说明握手阶段有额外排队。
websocket 子命令适合测长连接的持续抖动:
connproof websocket connproof.com --port 443 --timeout 60 --verbose它会持续发送小消息并测量往返时间,输出 P50、P95、P99 抖动。在 BBR 下,这个命令能捕捉到 ProbeBW 周期带来的抖动峰值。
dns 子命令在调优前后跑一次,排除 DNS 解析对连接建立时间的影响:
connproof dns connproof.com --timeout 5 --format json如果 DNS 解析耗时波动大,连接建立的抖动会被误判为拥塞控制问题。
调优决策树与失败边界
不是所有 LFN 都该上 BBR。决策依据是链路用途和缓冲区深度。
独占链路、大文件传输、能接受周期性延迟抖动,BBR 是最优选择。吞吐能到 Cubic 的 4 到 6 倍。
共享链路、实时业务为主、缓冲区深度超过 50ms,BBR 会把延迟成本转嫁给其他流。这种情况下要么用 BBR v2/v3(过冲幅度小),要么用 fq_codel 配合 Cubic,要么在 BBR 流上做速率限制。
浅缓冲区链路(小于 10ms 队列),BBR 的 ProbeBW 过冲会被丢包截断,BtlBw 估计偏高,实际吞吐可能不如 Cubic。这种链路上 BBR 的优势不明显。
与 Cubic 流竞争时,BBR 会抢占大部分带宽。如果链路上有必须保证的 Cubic 流(比如老设备),需要在交换机或路由器上做流分类和限速。
决策树:
1. 链路是否独占?
是 -> 2
否 -> 5
2. 缓冲区深度是否 > 50ms?
是 -> 3
否 -> 4
3. 业务是否容忍周期性延迟抖动?
是 -> BBR v3 + fq + tcp_notsent_lowat
否 -> BBR v2 或 Cubic + fq_codel
4. 缓冲区深度 < 10ms?
是 -> Cubic 可能更优,实测对比
否 -> BBR v1/v2
5. 是否有实时业务共存?
是 -> Cubic + fq_codel,或 BBR 流限速
否 -> BBR v32
3
4
5
6
7
8
9
10
11
12
13
14
15
16
失败边界要明确。BBR 在以下场景会退化:
- qdisc 不是 fq/fq_codel,pacing 失效,BBR 退化成突发发送,丢包率飙升。
- 应用层写入模式是突发大块,tcp_notsent_lowat 未设,发送队列积压,pacing 控制不住。
- 路径上有 ACK 聚合设备,BtlBw 估计被低估,吞吐上不去。
- 路径 RTT 变化剧烈(如卫星链路),RTprop 估计跟不上,inflight 上限失准。
- 接收端窗口不足,BBR 的 cwnd 被接收窗口限制,pacing 无效。
这些场景下先修基础设施,再谈算法选择。拥塞控制算法解决不了窗口不足、qdisc 配置错误、ACK 路径异常这些底层问题。
把评测做成可复现的流程
调优不是一次性动作。LFN 的路径质量会随路由变化、光缆维护、对端设备升级而变。建立一套可复现的评测流程比记住几个 sysctl 值更重要。
基线采集:在业务低峰期,用 Cubic 跑 60 秒 iperf3,同时抓包,记录吞吐、RTT P50/P95/P99、重传率、抖动。用 ConnProof diagnose 和 doctor 各跑一次,存 JSON。
变更实施:改 sysctl、换 qdisc、切拥塞控制算法。每次只改一个变量。
变更后采集:同样的 iperf3 参数、同样的抓包时长、同样的 ConnProof 命令。存 JSON。
对比分析:用脚本对比 JSON 里的关键字段。吞吐提升超过 20% 且重传率不增加,视为有效。RTT P99 增加超过 20%,需要评估是否可接受。
回滚条件:吞吐下降、重传率翻倍、RTT P99 超过业务阈值,任一触发就回滚。
这套流程的价值在于把“感觉快了”变成“P99 从 310ms 降到 260ms,重传率从 0.9% 降到 0.2%,吞吐从 1.2Gbps 升到 6.5Gbps”。数字能说服人,也能在出问题时快速定位是哪个变更引入的。
长连接的稳定性还依赖断连机制的正确配置。如果调优后连接频繁断开,先查 长连接断连机制分析 里的 keepalive 和超时设置,排除应用层和中间设备的干扰。晚高峰的拥塞表现与低峰期差异大,参考 晚高峰拥塞排查指引 里的分时采集方法,避免用低峰数据做全局决策。如果调优过程中出现连接被拒绝,TCP 连接被拒绝排查 覆盖了 ECONNREFUSED 的完整排查路径,先确认不是防火墙或 backlog 问题再继续。
BBR 与 Cubic 的选择最终是延迟与吞吐的权衡。LFN 把这两个指标的矛盾放到最大,也把算法的假设边界暴露得最清楚。理解 BtlBw 和 RTprop 的估计逻辑,理解四状态机的切换条件,理解 fq 和 tcp_notsent_lowat 对 pacing 的影响,才能在具体链路上做出有依据的决策。剩下的就是抓包、测速、对比、迭代。