TCP 吞吐与带宽时延积
高速链路为什么不一定给你高速传输:带宽时延积、窗口天花板、窗口扩大选项,以及一点点丢包所带来的惊人代价。
为什么高速链路也可能很慢
两个相隔 100 ms 的办公点之间有一条 1 Gbps 链路,本该在不到一秒内传完一个文件。实际上单条 TCP 连接往往只能跑到十分之一。问题不在链路 —— 是协议在等。TCP 要求数据被确认之后才能在途位置上被替换,而在长路径上,每一个确认都要走完一个完整的往返才能回来。如果发送方在第一个确认回来之前就用光了允许未确认的数据量,链路在每个往返里都会有一部分时间处于空闲。
决定这件事是否发生的量就是带宽时延积:要让管道被填满,需要有多少数据同时在途。它就是带宽乘以往返时延。
BDP(比特) = 带宽(比特/秒) × RTT(秒)
BDP(字节) = BDP(比特) ÷ 8
1 Gbps、RTT 100 ms:
BDP = 1,000,000,000 × 0.1 = 100,000,000 比特
= 100,000,000 ÷ 8 = 12,500,000 字节
= 12.5 MB也就是说,这条链路需要始终有 12.5 MB 的数据在途才能保持繁忙。这个数字就是后面每一项调优决策所追求的目标。
窗口天花板
TCP 发送方在途的未确认数据量,不能超过接收方通告的窗口允许的值。由此得出一个与链路速率毫无关系的吞吐上限:
吞吐(字节/秒) = 窗口(字节) ÷ RTT(秒)
窗口 65,535 字节、RTT 100 ms:
吞吐 = 65,535 ÷ 0.1 = 655,350 字节/秒
= 655,350 × 8 = 5,242,800 比特/秒 ≈ 5.24 Mbps这是 TCP 性能中最重要的一个数字。在 100 ms 路径上使用出厂默认的 65,535 字节窗口,会把单条流限制在约 5.24 Mbps —— 无论链路本身有多快。同样的窗口在 10 ms 的往返时延下能到 52.4 Mbps,这就是为什么这个问题在局域网上完全看不出来,而跨洋时却非常严重。
16 位带来的问题
TCP 首部中窗口字段原本只有 16 位宽。这不是实现选择,而是格式本身:能表达的最大值是 2^16 − 1 = 65,535。TCP 设计之初,这个值远大于任何路径的带宽时延积,所以不构成任何代价。而当链路在卫星或洲际距离上跑到几兆比特每秒之后,它就变成了新的硬约束。
窗口扩大:TCP 如何突破这个限制
RFC 7323(最早是 RFC 1323)引入了窗口扩大选项。握手期间双方各自通告一个移位计数,此后发送的每一个窗口值都按值 × 2^scale 解释。扩大因子为 7 时,天花板乘以 128:
| 扩大因子 | 倍数 | 有效窗口 | 吞吐天花板 |
|---|---|---|---|
| 0(不扩大) | ×1 | 65,535 B | 约 5.24 Mbps |
| 4 | ×16 | 1,048,560 B | 约 83.9 Mbps |
| 6 | ×64 | 4,194,240 B | 约 335 Mbps |
| 7 | ×128 | 8,388,480 B | 约 671 Mbps |
| 8 | ×256 | 16,776,960 B | 约 1.34 Gbps |
| 10 | ×1024 | 67,107,840 B | 约 5.37 Gbps |
由于移位计数是协商出来的,两端都必须支持 —— 一台现代 Linux 或 macOS 主机与老旧设备通信时,仍可能拿到未扩大的窗口。另外要注意,这个扩大因子只作用于窗口字段,不作用于握手期间窗口更新里的那个值,这正是读抓包时经常差 128 倍的经典来源。
丢包:另一个天花板
即便窗口很大,丢包也会施加自己的限制。拥塞控制把丢包视为拥塞的证据并降低发送速率,而恢复是渐进的。Mathis 公式给出了单条 Reno 式 AIMD 流的数量级上限:
吞吐 ≈ (MSS ÷ RTT) × (C ÷ √p)
MSS = 最大报文段长度(字节)
RTT = 往返时延(秒)
p = 丢包概率
C ≈ 1.22,对应简单的 AIMD(Reno)发送方
MSS 1460 B、RTT 100 ms、p = 1%:
1460 ÷ 0.1 = 14,600
1.22 ÷ √0.01 = 1.22 ÷ 0.1 = 12.2
吞吐 = 14,600 × 12.2 = 178,120 字节/秒
≈ 1.42 Mbps丢包项位于平方根之下,这让它的惩罚格外严厉。从 0.01% 的丢包升到 1% —— 增加一百倍 —— 只让吞吐下降十倍,而不是一百倍。这也意味着,一条存在少量持续性丢包的路径,有一个无论怎么调窗口都抬不起来的硬上限。
| 丢包率 | √p | C ÷ √p | 吞吐天花板 |
|---|---|---|---|
| 0.001%(1e-5) | 0.00316 | 386 | 约 45.1 Mbps |
| 0.01%(1e-4) | 0.01 | 122 | 约 14.3 Mbps |
| 0.1%(1e-3) | 0.0316 | 38.6 | 约 4.51 Mbps |
| 1%(1e-2) | 0.1 | 12.2 | 约 1.42 Mbps |
| 5%(5e-2) | 0.224 | 5.45 | 约 636 kbps |
真正卡住的是哪一个限制?
当传输比预期慢时,真正有用的问题是:这三个限制里,哪一个正在起约束作用。每一个的应对方式都不同,用错了只是浪费时间。
| 症状 | 可能的限制 | 该检查什么 |
|---|---|---|
| 吞吐 × RTT ≈ 64 KB | 窗口,且未启用扩大 | 握手过程中双方是否都出现了窗口扩大选项? |
| 吞吐 × RTT ≈ 某个整数 MB | 窗口,容量小于 BDP | 把通告的窗口与 BDP 作比较;检查 tcp_rmem / tcp_wmem 自动调优。 |
| 吞吐 × RTT ≈ BDP,但依然很慢 | 丢包 | 查重传与重复 ACK;要在路径上测丢包,而不是只在两端测。 |
| 吞吐远低于窗口与丢包两个天花板 | 应用程序或磁盘 | 可能是发送方没有数据可发,或者接收方写入太慢。 |
| 单条流慢,多条流合计很快 | 每流限制,而不是链路 | 多条流各有自己的窗口;用单条流测往往测错了对象。 |
最快的粗判方法:把观测到的吞吐乘以 RTT。如果结果可疑地接近 64 KB,或者接近某个整数的兆字节数,那限制就在窗口。如果接近 BDP 却远低于链路速率,就去查丢包。
这个模型没有覆盖什么
上面的数字都是单条理想化流的上限。实际传输还会受 TCP 慢启动限制 —— 连接以一个很小的拥塞窗口开始,每个往返翻一倍,直到达到路径容量,所以短传输根本摸不到天花板。延迟确认、接收窗口自动调优、竞争流量、负载下排队时延推高实际 RTT,以及 CUBIC 对丢包的不同反应,都会让真实行为偏离模型。
请用这些数字判断"哪个限制在起作用"以及"调优还能换来多少余量",而不是用它预测某次具体传输要多久。带宽计算器处理的是更基础的问题:给定速率下传一个文件要多久;而 MTU / MSS 计算器处理的是会降低这些公式所依赖的 MSS 的封装开销。
带宽时延积参考
填满常见链路需要多少数据在途。这也是单条 TCP 流跑到满速所需的窗口大小 —— 把它和未扩大时的 65,535 字节上限对比,就能看出什么时候必须启用窗口扩大。
| 链路速率 | 1 ms | 10 ms | 50 ms | 100 ms | 200 ms |
|---|---|---|---|---|---|
| 10 Mbps | 1.25 KB | 12.5 KB | 62.5 KB | 125 KB | 250 KB |
| 100 Mbps | 12.5 KB | 125 KB | 625 KB | 1.25 MB | 2.5 MB |
| 1 Gbps | 125 KB | 1.25 MB | 6.25 MB | 12.5 MB | 25 MB |
| 10 Gbps | 1.25 MB | 12.5 MB | 62.5 MB | 125 MB | 250 MB |
| 100 Gbps | 12.5 MB | 125 MB | 625 MB | 1.25 GB | 2.5 GB |
所有大于 65,535 字节的值,单条 TCP 流在不使用窗口扩大选项时都达不到。这张表里除了第一列之外几乎每一格都超过了它 —— 这就是该选项在现代互联网上实际上是强制要求的原因。
值得记住的关键常量
| 量 | 数值 | 为什么重要 |
|---|---|---|
| 未扩大时的最大窗口 | 65,535 字节 | 16 位字段的上限;不启用扩大时就是天花板 |
| 窗口扩大的最大因子 | ×2^14 | RFC 7323 把移位上限设为 14,最大窗口约 1 GB |
| 以太网默认 MSS | 1460 字节 | 1500 MTU − 20 IPv4 − 20 TCP |
| Mathis 常量 C | 约 1.22 | 对应简单 AIMD(Reno)发送方的经验系数 |
| 建议的最小 MTU | 576 字节(IPv4) | 任何路径都必须能在不分片的情况下转发它 |
| IPv6 最小 MTU | 1280 字节 | RFC 8200 抬高了底线;路由器从不分片 |
常见问题
带宽时延积是什么?
带宽时延积(BDP)是为了让链路完全跑满,需要同时在途的数据量 —— 已发出但还没被确认。它就是带宽乘以往返时延。1 Gbps 链路、100 ms RTT 的 BDP 是 1e9 × 0.1 = 1e8 比特 = 12.5 MB。在线上有 12.5 MB 未确认数据之前,链路总有一部分时间处于空闲。
为什么吞吐会被 TCP 窗口卡住?
发送方的未确认在途数据不能超过接收窗口允许的量。若窗口为 W 字节、往返时延为 R 秒,则每个 RTT 最多只能有 W 字节通过链路,所以吞吐上限是 W ÷ R。窗口 65,535 字节、RTT 100 ms 时,这个上限是 655,350 字节/秒 ≈ 5.24 Mbps —— 与底层链路有多快无关。
TCP 窗口扩大是什么,为什么需要它?
原始 TCP 首部中窗口字段只有 16 位,所以在没有扩展选项时最大窗口是 65,535 字节。在长距离或高速链路上,这个天花板会把吞吐压得很惨。RFC 7323 引入了窗口扩大选项,把通告值按协商好的位数左移 —— 扩大因子 7 会把 65,535 变成 8,388,480 字节。两端都必须支持,否则不会启用。
丢包为什么对吞吐影响这么大?
拥塞控制把丢包解读为需要把发送速率减半的信号,而恢复过程是缓慢的。Mathis 公式把由此产生的上限近似为 (MSS ÷ RTT) × (1.22 ÷ √p),其中 p 是丢包概率。由于丢包项在平方根之下,在 100 ms RTT 路径上 1% 的丢包率,会把一条 MSS 1460 字节的单流限制在约 2.8 Mbps —— 哪怕链路是千兆的。
什么是长肥网络?
长肥网络(LFN,long fat network / long fat pipe)指带宽时延积很大的路径 —— 通常是跨大陆或跨洲链路,带宽与 RTT 的乘积很大。这类路径正是窗口扩大、选择确认与更大缓冲区最关键的场合,也正是在这里,未经调优的主机性能会远低于链路容量。
窗口越大就一定越快吗?
不是。窗口必须有两端真实的缓冲区和内核自动调优上限作为支撑,而且只有当路径确实能把数据送过去时才有意义。在短距离或丢包路径上,更大的窗口主要只是增加了丢包后需要重传的数据量。窗口应当按带宽时延积来定,不要更大。
为什么这些计算器给的是上限而不是预测值?
它们建模的是一条理想化的单流:没有竞争流量、没有排队、没有丢包。真实吞吐会因慢启动、拥塞控制爬升、延迟确认、接收窗口自动调优、竞争流以及应用层瓶颈而更低。请把这些数字当作调优能够逼近的天花板,以及判断「到底是窗口、丢包还是路径在起限制作用」的工具。