Skip to content

第24章 延迟、吞吐量和带宽时延积(BDP)

一条链路*标称带宽(Bandwidth)*明明有 1 Gbit/s,可跨地域复制文件时却只能跑到几十 Mbit/s?带宽决定了链路每秒理论上能传送多少比特,而 TCP 想要真正跑满带宽,就必须在每个 RTT(往返时延)内保持足够多的“在途数据”(Data in Flight)。最终的实际吞吐量(Throughput),由窗口大小、往返时延、丢包恢复机制、传输规模以及应用层的供数能力共同决定。

带宽是什么?

带宽表示链路或路径单位时间内可承载数据的速率上限,常用 bit/s、Mbit/s 或 Gbit/s 表示。端到端路径的可用带宽通常受最窄链路、共享流量和限速策略约束。

网卡协商出的标称速率只描述本地链路能力,不能直接代表跨地域连接能获得的实际速率。

RTT 是什么?

RTT(Round Trip Time,往返时延)是信息从一端出发并收到对端响应或确认所经历的时间。TCP 可以根据已发送序列范围和 ACK 到达时间估计 RTT,并据此调整重传计时器。

RTT 包含去程与回程的传播、排队和处理时间,不等同于单向时延,也不等于完整业务请求耗时。

吞吐量是什么?

吞吐量是单位时间内通过某个测量边界的数据量。测量边界可以位于应用、TCP、IP、链路或物理层,因此报告必须说明统计了哪些字节以及起止时间。

吞吐量低于标称带宽很常见,窗口、RTT、丢包、协议开销和应用供数速度都可能成为限制。

先统一三个核心指标

延迟(Latency)

在 TCP 分析中,最常用的延迟指标是 RTT(Round Trip Time,往返时延),也就是从发送数据到收到对应 ACK 的时间。不过,应用层感受到的请求延迟远不止 RTT,它还包括了网络排队、协议交互的轮次、服务端的业务处理时间以及客户端的调度开销。由于精确计算单向延迟要求两端主机的时钟绝对同步,在实际排查中,通过单侧抓包来计算 RTT 往往更靠谱。

我们可以把一条路径的总延迟拆解为以下几个直观的部分:

  • 传播延迟(Propagation Delay):电信号或光信号在物理介质中传输所需的时间。
  • 序列化延迟(Serialization Delay):网卡把一个数据包(报文)的所有比特全部推到物理链路上所需的时间。
  • 排队延迟(Queuing Delay):数据包在路由器或交换机的缓冲队列中等待处理的时间。
  • 主机处理延迟(Processing Delay):操作系统的协议栈、应用程序以及 CPU 调度所消耗的时间。

举个例子,在一条 10 Mbit/s 的链路上发送一个 1500 字节的数据包,它的序列化时间大概是:

1500×8 bit10000000 bit/s=0.0012 s=1.2 ms

只要升级带宽,就能大幅缩短序列化时间;但在长距离传输时,地理跨度带来的传播延迟和网络拥塞引发的排队延迟,依然是不可忽视的大头。

吞吐量(Throughput)与有效吞吐(Goodput)

讨论吞吐量时,一定要先明确“在哪个网络层级”测量。物理链路层的吞吐量会把以太网(Ethernet)帧头、IP 头、TCP 头甚至重传的数据包都算进去;而应用层真正关心的往往是有效吞吐(Goodput),即成功交付给应用程序的有效字节数除以总耗时:

Goodput 是什么?

Goodput(有效吞吐)只统计在给定时间内成功交付给应用的有效业务数据。协议首部、握手、重传以及重复发送通常不计入分子。

同一传输的链路吞吐可能很高而 Goodput 较低,例如大量重传或应用协议开销占比较大时。

goodput=成功交付的应用字节传输持续时间

日常排查时你会发现,iperf3 测出的数值、应用日志记录的速度,以及 Wireshark I/O Graph 画出的图表,结果常常对不上。这是因为它们使用的“分子”(算哪些数据)和“时间边界”(从何时算起)都不一样。只有在报告里明确写出数据的单位、网络层级以及起止事件,吞吐量的数据才有对比价值。

瓶颈带宽(Bottleneck Bandwidth)

端到端的网络路径是由多条链路、各种队列和设备串联起来的,其中“最窄”的那一段(即长期服务速率最低的环节),就构成了整条路径的瓶颈带宽。即便发送端的网卡显示连着 1 Gbit/s 的网络,那也仅仅代表本地接口的速率。实际上,中间的 VPN 隧道、Wi-Fi 信号、网关的流量整形规则、跨地域骨干网,甚至是接收端的入口带宽,都可能成为真正的“木桶短板”。

BDP:链路到底能容纳多少“在途数据”

带宽时延积(Bandwidth-Delay Product,BDP) 是衡量网络容量的关键指标,它的核心公式非常简单:

BDP 是什么?

BDP 等于瓶颈带宽乘以 RTT,表示为了持续填满路径,发送方大约需要维持多少尚未确认的在途数据。计算时必须先统一 bit、byte、秒等单位。

有效发送窗口明显小于 BDP 时,单连接吞吐上限会接近“窗口 ÷ RTT”;BDP 也只是尺度估计,丢包、应用供数和拥塞控制仍会影响结果。

BDP=瓶颈带宽×RTT

计算前,切记先统一单位。以一条 100 Mbit/s、RTT 为 80 ms 的网络路径为例:

100×106 bit/s×0.080 s=8×106 bit

换算成字节:

8×1068=1000000 byte0.954 MiB

这个数字告诉你:发送端必须在网络中维持大约 1 MB 尚未被确认的“在途数据”(Data in Flight),利用 ACK 时钟机制(ACK Clocking),才能勉强跑满这 100 Mbit/s 的带宽。

如果换成 1 Gbit/s 且 RTT 达到 100 ms 的跨国专线:

1×109×0.1=100×106 bit=12.5 MB11.92 MiB

这意味着,要跑满这条专线,你需要近 12 MB 的在途数据!这种“高带宽、高延迟”的网络在业界被称为 LFN(Long Fat Network,长肥网络)。对于 LFN 来说,TCP 必须依靠 Window Scale 选项来放大接收窗口(rwnd),同时拥塞控制算法也需要花费更多时间,才能把拥塞窗口(cwnd)慢慢“撑”到足够的水平。

Long Fat Network 是什么?

LFN 指带宽与时延都较高、因而 BDP 很大的网络,例如跨洲高速专线或卫星链路。这里的 “fat” 描述高带宽,“long” 描述较长 RTT。

这类路径需要更大的窗口和缓冲尺度,也会放大慢启动与丢包恢复的时间成本。

避免踩坑:单位换算的固定流程

在网络性能排查中,最容易让人翻车的低级错误,就是把 bit(比特)byte(字节)、*十进制前缀(如 MB、Mbit/s)二进制前缀(如 MiB、KiB)*混为一谈。

bit 与 byte 有什么关系?

bit 是一个二进制位,byte 是字节,通常由 8 bit 组成。网络速率习惯用 bit/s,文件大小和应用缓冲区习惯用 byte;从 bit/s 换算为 byte/s 需要除以 8。

大小写也有意义:b 常表示 bit,B 常表示 byte,例如 80 Mbit/s = 10 MB/s

Mbit/s、MB/s、MiB/s 与 KiB 分别是什么?

Mbit/s 是每秒 106 bit,MB/s 是每秒 106 byte;MiB/s 是每秒 220 byte,KiB210 byte。十进制前缀适合链路速率,二进制前缀常见于内存和文件工具。

性能报告保留原始单位与换算过程,可以避免 8 倍以及十进制/二进制前缀造成的差异。

为了避免算错,建议你固定采用以下四步法:

  1. 先把带宽统一换算成 bit/s
  2. 把 RTT 统一换算成 s(秒)
  3. 两者相乘,得到单位为 bit 的结果。
  4. 最后除以 8,得到 byte。在汇报时,最好同时列出十进制(MB)和二进制(MiB)的结果。

背熟这组常用换算关系:

名称数值
1 byte8 bit
1 Mbit/s106 bit/s
1 MB106 byte
1 MiB220=1048576 byte
1 ms103 s

举个例子:25 Mbit/s 与 40 ms 相乘。 正确算式是 25×106×0.04=1×106 bit,除以 8 得到 125,000 byte(约 122 KiB)。如果你一上来就把 25 Mbit/s 当成了 25 MB/s,那结果就会整整夸大 8 倍!一个小技巧是:在草稿纸上写算式时,每一步都带上单位,这样极易在约分时揪出错误。

需要特别指出的是,BDP 计算的仅仅是单向维持 ACK 流水线所需的在途数据量。由于 TCP 是全双工协议,连接的收发两个方向有着各自的带宽速率、接收窗口和拥塞状态,因此必须分开独立计算。而且,反向路径上 ACK 报文经历的延迟,天然就已经包含在你测量出的正向 RTT 里面了。

在向团队提交性能报告时,建议同时写明 Mbit/sMiB/s。比如 80 Mbit/s 除以 8 等于 10 MB/s,再除以 1.048576 就是 9.54 MiB/s。有了 MiB/s 这个单位,开发者就能直接用应用日志里记录的“文件大小”除以“耗时”来进行交叉核对。保留完整的原始单位和换算过程,也能大大降低同事们帮你复核时的沟通成本。

窗口大小是如何卡住吞吐量上限的?

如果网络带宽很足,但速度死活上不去,那么问题往往出在“窗口”上。在稳定传输状态下,我们可以用一个简单的公式来估算吞吐量的极限:

throughputmin(rwnd,cwnd)RTT

也就是说,吞吐量受制于接收窗口(rwnd)和拥塞窗口(cwnd)中的较小值。

假设双方协商出的有效窗口只有 256 KiB,而 RTT 是 80 ms:

256×1024 byte0.08 s=3276800 byte/s

折算成带宽:

3276800×8=26214400 bit/s26.2 Mbit/s

这说明什么?说明就算你拉了一条 100 Mbit/s 的专线,受限于这区区 256 KiB 的窗口,你的理想下载速度最多也就只能跑到 26.2 Mbit/s!想要真正榨干链路容量,不仅 rwndcwnd 得撑大到 1 MB 左右,应用程序也必须能持续高速地“喂数据”(供数),并且还要祈祷网络中不要出现严重的丢包重传。

另外,如果你抓包看到的 RTT 包含了中间路由器“深队列”(Deep Queue)排队所产生的严重延迟(即 Bufferbloat 现象),那么用这个“虚高”的 RTT 算出来的 BDP,其实是把交换机的队列容量也算进去了。因此,在做性能建模时,高阶排障人员通常会同时记录 最小 RTT(Min RTT)当前 RTT:最小值最能反映纯粹的物理传播延迟,而两者之间的差值,则能敏锐地揭示出网络队列积压的程度。

Bufferbloat 是什么?

Bufferbloat 指网络设备或主机缓冲区过深,拥塞时积压大量数据并造成持续高排队延迟的现象。此时吞吐量可能仍然很高,交互请求、语音和游戏延迟却显著恶化。

比较空闲时的最小 RTT 与负载下 RTT,配合队列和丢包信息,可以识别这种“带宽尚可、时延膨胀”的问题。

不同业务形态面临的性能瓶颈

不同的业务场景,卡脖子的地方截然不同。下表总结了常见场景的核心瓶颈与优化方向:

场景核心痛点与关注指标有效的优化方向
大文件长连接传输BDP、窗口大小、丢包率、磁盘I/O与应用层供数速度配置足够的 TCP 缓冲、维持数据流水线、精准定位并解决重传问题
小文件短连接传输频繁的三次握手开销、慢启动(Slow Start)限制使用连接池复用(Keep-Alive)、尽量减少协议交互轮次
小请求串行执行每次请求硬吃完整的 RTT 和服务端处理时间引入请求流水线(Pipelining)、合理调大并发、改用批处理(Batching)
高 RTT 网络(跨洲/卫星)巨大的 BDP 需求、丢包恢复成本极高、窗口扩展受限开启 Window Scale、选用专为高延迟设计的拥塞控制算法(如 BBR)、保持稳定供数
接收端应用消费慢rwnd 被塞满导致 Zero Window(零窗口)、业务队列阻塞完善端到端的背压机制(Backpressure)、限制队列深度、优化代码提升消费能力

让我们算一笔账:假设纯网络的 RTT 是 20 ms。如果你的代码写得很糙,100 个请求完全串行执行,非要等前一个响应回来才发下一个。光是消耗在纯网络传输上的轮次时间就已经有:

100×20 ms=2 s

如果在协议设计上允许并发,比如 10 个请求并发发送,那么理想情况下只需要 10 轮交互,网络传输时间瞬间降到 0.2 秒!并发连接(或 HTTP/2 多路复用)能够迅速在网络中“铺满”足够多的在途数据,从而有效利用带宽。不过代价是:瞬间的并发会给服务端带来极大的负载压力,更容易引发队列溢出和公平性问题,因此在做系统容量规划时必须全局考量。

此外,慢启动(Slow Start) 机制对于短命的连接来说简直是“降维打击”。对于几十 KB 的小文件,可能连接还没等 cwnd 涨起来,数据就已经传完了。此时虽然你的网速有百兆,却根本发挥不出来。而大文件传输则有充足的 RTT 轮次,能让拥塞窗口逐步撑大,最终进入平稳的高速状态。最后提醒一点:应用层每次调用 send() / write() 传入的数据块大小(Buffer Size),主要影响的是系统调用频率、内存拷贝开销以及供数的连续性;至于到了线上抓包时看到的真实 TCP 报文段(Segment)到底被切成了多大,那是由操作系统的 TCP 协议栈、MSS 协商以及 MTU 共同决定的。

实战演练:探究 RTT、并发度与读写块大小对吞吐的影响

这里我们继续复用前面章节搭建的 tcp-rx 实验拓扑。如果第 23 章的服务端还在运行,可以直接复用;如果端口空闲,就在终端 A 中启动 iperf3 服务端:

bash
sudo ip netns exec tcp-rx iperf3 -s -B 10.200.20.2

在终端 B 中开启全局抓包,将所有报文记录下来:

bash
sudo tcpdump -i veth-tx -s 0 -w chapter24.pcap 'tcp port 5201'

最后在终端 C 运行测试。下面这组脚本会将网络正向的瓶颈带宽严格限制为 50 Mbit/s,然后依次注入 10 ms、50 ms 和 100 ms 的单向延迟,分别模拟出大概 20 ms、100 ms 和 200 ms 的基础 RTT。每一轮都会固定传输 64 MiB 的数据:

bash
for one_way in 10ms 50ms 100ms; do
  sudo tc qdisc replace dev veth-tx root netem delay "$one_way" rate 50mbit limit 10000
  sudo ip netns exec tcp-rx tc qdisc replace dev veth-rx root netem delay "$one_way" limit 10000

  iperf3 -c 10.200.20.2 -n 64M -P 1 -l 128K -i 0.5
  iperf3 -c 10.200.20.2 -n 64M -P 4 -l 128K -i 0.5
  iperf3 -c 10.200.20.2 -n 64M -P 1 -l 4K -i 0.5
done

在上面的命令中,-P 4 代表创建 4 条并行的 TCP 连接;-l 用于控制 iperf3 在应用层调用读写时的 Buffer(缓冲区)大小,需要注意的是,这并不能决定线上实际发出的 TCP 报文段(Segment)大小。

在 50 Mbit/s 的带宽下,这三种 RTT 对应的理论 BDP 如下:

50 Mbit/s 路径计算公式理论 BDP
RTT 20 ms50×106×0.02/8125,000 B,约 122 KiB
RTT 100 ms50×106×0.10/8625,000 B,约 610 KiB
RTT 200 ms50×106×0.20/81,250,000 B,约 1.19 MiB

实验记录表

建议你在测试时边跑边记下以下关键字段:

RTT并行数读写块 (-l)总耗时应用层吞吐重传次数峰值 cwnd(段数/字节数)rwnd 最小值
20 ms1128 KiB
100 ms1128 KiB
200 ms1128 KiB
各 RTT4128 KiB
各 RTT14 KiB

测试进行时,可以在另一个终端用 ss -ti dst 10.200.20.2 命令来采样观察当前的 RTT、cwnd、MSS、Pacing 速率以及重传统计。在 Linux 的 ss 输出中,cwnd 的单位通常是“报文段个数”(Segments),你需要用它乘以 MSS 才能估算出近似的拥塞窗口字节数。抓包结束后,把 pcap 扔进 Wireshark,通过菜单栏 Statistics → TCP Stream Graphs → Throughput 可以直观看到吞吐量随时间爬升的曲线,并能通过查阅 Seq/Ack 号的差值计算出任意时刻的实际在途字节数(Bytes in Flight)。

实验现象剖析

  • 当 RTT 变大后,发送端每收到一轮 ACK 确认都需要更漫长的等待;对于 64 MiB 这种固定大小的传输任务,极易陷入慢启动阶段或被窗口卡住,导致连带宽的一半都没跑满就结束了。
  • 采用 4 条并行连接时,每条 TCP 连接都在独立维护自己的 cwnd,它们聚合起来的总在途数据量能极快地触及并填满 BDP。不过再怎么并发,整体吞吐量依然被底层的 50 Mbit/s 物理瓶颈死死掐住。
  • 如果应用层强行把读写块缩得很小(如 4 KiB),会导致系统调用极为频繁,应用层“供数”可能会变得断断续续;具体的恶化程度取决于你用的编程语言、CPU 性能以及内核的缓冲策略。
  • 结论:只有当接收窗口(rwnd)和拥塞窗口(cwnd)双双达到 BDP 的量级,且应用能源源不断地高效供数时,长连接的下载速度才有机会真正逼近物理链路的瓶颈上限。

测试完毕后,别忘了在服务端和抓包终端按 Ctrl+C 退出,并执行以下命令把系统环境恢复原样:

bash
sudo tc qdisc del dev veth-tx root
sudo ip netns exec tcp-rx tc qdisc del dev veth-rx root
sudo ip netns del tcp-rx

如果在你的测试环境中用不了 netem 模拟延迟,那你可以直接找两台 RTT 差异较大的云服务器(比如一台同城、一台跨国),运行相同的传输测试来进行对比。或者在一台远端机器上,比较“单线程串行下载”和“多线程并发下载”的差异。在排查真实业务问题时,可以通过 Wireshark 抓取三次握手的耗时,或者结合 ss -ti 测算出 RTT;用实测吞吐量反推瓶颈,最后算出目标 BDP,看看到底是网络窗口被卡主了,还是应用层拖了后腿。如果是 Windows 环境,Wireshark 的 I/O Graph 结合应用的业务日志以及 PowerShell 的时间戳统计,同样是非常给力的排障铁证。

吞吐量上不去?排障思路六步走

排查低吞吐率问题时,建议严格按照以下顺序“排雷”:

  1. 先算对账:用业务层真实收到的字节数除以总耗时,算一下应用有效吞吐(Goodput),并明确标出使用的单位是 Mbit/s 还是 MiB/s。
  2. 算目标 BDP:摸清网络的底子,记录好最小 RTT(物理延迟基线)、当前 RTT 和预估的瓶颈带宽,套公式算出理论上需要填满的 BDP。
  3. 抓包对线:看抓包里的数据。把你实际看到的缩放后接收窗口(rwnd)、发送端拥塞窗口(cwnd,用 ss 看)以及真实的在途数据量(Bytes in Flight)跟算出来的理论 BDP 做对比,看看差了多少。
  4. 抓网络异常:仔细排查抓包文件里的重传(Retransmission)、SACK(选择性确认)以及 RTO(超时重传),看看网络拥塞事件是不是把辛辛苦苦涨起来的窗口给“打趴下”了。
  5. 排查应用层:如果网络参数一切正常,那就去排查你的代码:是不是发端应用供数不积极?收端消费不及时?或者干脆是机器的 CPU 负载和磁盘 I/O 已经拉满了?
  6. 控制变量法:做调优实验时,每次只改一个变量(比如只改 Buffer 大小或只改并发数),跑完留存对比基线和完整的时间序列数据。

灵魂拷问(理解检查)

  1. 带宽 200 Mbit/s、RTT 40 ms,它的 BDP 分别是多少比特(bit)和十进制兆字节(MB)?
  2. 如果协商出的窗口大小死死卡在 512 KiB,且 RTT 为 100 ms,那么在此窗口限制下,理想极限吞吐量大概是多少 Mbit/s?
  3. 排障时发现当前 RTT 涨到了 80 ms,而平时测出来的最小 RTT 只有 30 ms。这多出来的 50 ms 延迟暗示了网络路径上可能正在发生什么?
  4. 为什么 10 KB 的短小请求和 10 GB 的大文件下载,明明跑在同一根网线上,前者的带宽利用率却惨不忍睹?
  5. 敲下 iperf3 -l 4K 到底在应用层控制了什么参数?真正跑到网线上的 TCP 报文段(Segment)大小,又是被哪些底层机制决定的?

本章小结

**带宽时延积(BDP)**的本质,就是把瓶颈带宽和网络延迟整合起来,量化出一条网络路径“需要多少在途数据才能被彻底喂饱”。当 TCP 的有效窗口小于 BDP 时,吞吐量就会被“窗口大小除以 RTT”的物理天花板锁死;而在窗口放开后,决定最终网速的接力棒,就会交到拥塞控制算法、丢包率、应用层供数效率以及机器的处理资源手里。 记住,要想得出靠谱的性能结论,必须严谨对齐计量单位、明确测量的网络层级,并综合使用应用日志、端点 ss 状态记录和真实的 pcap 抓包文件,形成完整的证据链。

规范依据

导航

上一章:第23章 拥塞控制 · 所属篇:第五篇 · 下一章:第25章 Socket API 的正确使用 · 教程总览

Licensed under CC BY-NC-SA 4.0.