提高带宽利用率!为什么要Pacing?

探讨了TCP协议从AIMD模型到Pacing方法的演变,分析了如何提高单条TCP连接的带宽利用率,以及Pacing在解决网络拥塞问题上的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1986年的TCP拥塞崩溃事件让AIMD模型在1988年后出来应对时局,从此以后互联网协议的设计者和实现者聚焦于如何让网络不拥塞。

毫无疑问,这里最重要的是公平性,而非效率。不管是慢启动,加性增窗,乘性减窗,还是后来的Vegas算法的主动退让,其目标都在于保证多条流经过共享链路时能公平共享带宽。这种机制的目标不是让单条流跑得更快。

换句话说,1988年的模型是不患寡而患不均的模型。其中的 “不患寡” 给很多人带来了误解。


事情在2010年前后悄悄地起了变化。


AIMD模型的目标是不患寡,而患不均,这注定单条TCP的带宽利用率极低,因此HTTP协议一般会采用多条TCP流捆绑的方式来传输Web服务器的数据以增加带宽,然而多TCP连接意味着连接管理的开销会增大,同时数据同步的开销也会增加,这导致了人们倾向于使用单独的连接来完成所有的事情。

然而,又是一个然而,TCP固有的队头拥塞问题导致单独的TCP连接无法很好的进行HTTP流的多路复用,这个难题促进了Google对QUIC协议的设计和发布,同时,QUIC的一些显而易见的优势点也逐步的回移进了TCP(如果可能的话)。

不管怎样,如今确实是倾向于减少TCP连接数量,这便弱化了公平性的约束,如今,提高单条连接的带宽利用率,成了迫在眉睫的硬需求。


如何度量带宽利用率?

我倾向于用一种 综合的效用 来度量,而不仅仅是 把带宽跑满, 倘若如此,一个包发十遍肯定能把带宽跑满,然而此时的利用率高吗?

我倾向于把代价也算进去,最终,我们需要,在带宽尽可能大的同时,保证代价尽可能小。

GGG为收益,PPP为代价或者成本,那么一个综合效用可以用以下的比值表示:

E=GPE=\dfrac{G}{P}E=PG

我们的任务是,求出一组约束,使得EEE的值最大,此时带宽的利用率最大。

上面的式子中,请注意PPP由两部分组成:

  • 待服务数据包的排队时延成本;
  • 链路转发节点的空置时间成本;

大致画图如下:
在这里插入图片描述

本文以下的讨论,假设链路的处理带宽为常数UUU,处理方式为固定速率处理,处理单位为包粒度而不是字节,那么处理一个包的时间便是1U\dfrac{1}{U}U1,我们从一种极端情况开始,经由一种一般的情况,最终会得出结论,只有均匀按照每1U\dfrac{1}{U}U1时间匀速到达的数据包,其EEE值最大,而这种方式就是Pacing!


先看以极端情况下完全突发到达的数据包,即当到达率为λ\lambdaλ时,单位时间内所有的λ\lambdaλ个数据包同时到达。

我们知道,单位时间内系统可以处理UUU个数据包,是为服务率为UUU,然而当λ\lambdaλ个数据包在1U\dfrac{1}{U}U1时间同时到达时,必然会有λ−1\lambda-1λ1个数据包排队,而单位时间的空置时延依然是1−λU1-\dfrac{\lambda}{U}1Uλ,因此总代价就是常数。如下图所示:

在这里插入图片描述

虽然看样子利用率接近了100%,然而代价确实巨大的!通过计算可知,这种情况下的EEE值并不高。然而这么多年,TCP的AIMD正是工作在这个模型上。

然而,正如我们分析网络数据流看到的那样,情况可能比上面的模型展示的结果更加糟糕,这是为什么?我们来接着分析。

下面我们看一种更加一般,更加符合真实环境的情况,即泊松到达的情况。这里将会揭示一个关于 100%利用率的神话永不可达 的事实:

在这里插入图片描述

至于说泊松到达的情况下,这张图为什么长这个样子,我就不推导了,从排队论的公式简单理解作图就能得到。注意这里的事实,最佳利用率而不是最大利用率,为什么?

从图上可以看出,如果我们在泊松到达的情况下想逼近收益墙附近的最大100%利用率,代价将是无法承受的,我们看到队列长度将趋向无穷大,这是根本无法处理的情况!

这背后的原因其实非常简单,即在泊松到达到达的情况下,单位时间到达的数据包会有概率性的大于μ\muμ,此时就将付出排队延时的代价,而同样会有概率性的小于μ\muμ,此时便要付出空置的代价,而不管是排队还是空置,对于服务方而言,都是成本,不幸的是,这两种成本是叠加而非抵消的关系!它们对数据包到达的影响效果仅仅是在数值意义上使得其到达均值为到达率λ\lambdaλ

这便是100%利用率永不可达的神话!

这个故事不断在我们生活中上演,这也是为什么运维们很怕CPU利用率达到100%的原因,100%并不意味着系统利用率高,而是意味着系统卡死!

同样,我们的工作看上去永远也做不完,因为你永远不知道接下来会发生什么,只要你没有空闲的时候,那么就意味着你将永远没有空闲的时候。你仔细想想,是不是这个道理。


如何解决这个问题呢?

破除泊松到达模式即可,取而代之,我们要适配固定的服务速率,即带宽μ\muμ,如果我们能完全按照每1μ\dfrac{1}{\mu}μ1的时间发送一个数据包,那岂不是意味着 既不用排队,也不会空置 了吗?这才是真正的100%的利用率!
在这里插入图片描述
是的,这就是Pacing!

完全按照瓶颈带宽来空置发送的速率,使其和服务速率完全相匹配!这就是提高带宽利用率的最佳方法。上图也是TCP BBR的理论基础。


然而,说起来容易做起来难,首先,端到端的TCP如何知道带宽到底是多少?你又要说测量是不是?测量结果具有一个RTT的滞后性。因此你测得的结果永远都是以前的结果而不是现在的结果,由于网络情况存在随机性,更无法预测未来的情景,于是乎,完全按照瓶颈带宽来发送数据是不可能的,我们意识到一旦不可能实现这一点,就意味着会出现随机的排队或者资源空置,这无可避免!

其次,Pacing的方法增加了TCP端到端实现的复杂性,破坏了TCP完美的 自时钟。这个倒是有几句话可以说说。

事实上TCP的AIMD在设计之初就将Pacing的希望寄托于其ACK自时钟了。因为当时的设计者们相信,完美的 雅各布森管道 会把突发出去的数据包整成Pacing的方式到达对端,进而针对这些数据包ACK也会是Pacing的方式到达发送端,从而驱动下一轮的数据以Pacing的方式发送。

以上这个过程详见《TCP/IP详解(卷1)》。

然而事实证明,TCP的雅各布森管道并不完美,很少有网络能把突发的TCP数据整成Pacing的,最终,TCP便背上了突发协议的骂名。那么,雅各布森管道为什么不完美?有以下原因:

  • 网络上存在多个TCP流而不是一个,新流的出现和旧流的消失不可预知;
  • ACK的反向路径与数据包正向路径不对称导致形成的Pacing序列被破坏;
  • ACK本身的丢失会导致突发;
  • 一些路由器为了限制包量会根据TCP的积累应答机制将多个连续ACK聚合,造成突发;

总之,网络链路太复杂,这使得依赖自时钟形成Pacing是不现实的,那么就不得不自己去算Pacing Rate了,而这个结果又是滞后的,或者说我们采集到的信息仅仅够算一个所谓的均值,这些信息远远不足以指导Pacing发送。

不管怎么样,我们依然会选择Pacing的方式来发送数据,至少在没有新的有别于AIMD的全新数学模型出现之前吧。

如此一来,只要我们选择了Pacing,那么就必然要在TCP发送端内置一套数据采集和计算引擎,这是AIMD模型所不需要的。


其实,很早以前就曾出现过一些争论,比如关于完全速率控制的Pacing Rate发送和完全ACK自时钟驱动的发送之间的争论,非常类似于中断和轮询之间的争论,最终,我们现在使用的Pacing发送,比如BBR的Pacing方式,事实上是融合了两者:

  • 用ACK来驱动Pacing Rate的计算;
  • 基于Pacing Rate来平滑发送数据。

总之,提高带宽的利用率的方法已经从捆绑多条TCP连接的方式进化到了提高单条TCP连接的带宽利用率。理论上证明Pacing的方法是最好的,但是如何精确计算Pacing却成了新的问题。

那么怎么办?

<think>嗯,用户想了解Linux内核中'tcpi_snd_cwnd'这个参数的含义和技术用途。从用户提供的引用材料来看,ta应该是个开发者或网络工程师,正在调试TCP性能问题。用户引用了四份技术资料,其中包含内核头文件、代码片段和文档说明,说明ta已经做过初步研究但需要更系统的解释。 用户特别标注了"系统级指令",要求所有数学公式必须用$...$格式。虽然当前问题不涉及复杂公式,但后续解释拥塞窗口变化时可能需要用$cwnd_{new} = cwnd + \frac{MSS \times MSS}{cwnd}$这样的公式。另外需要确保回答中引用的每段资料都加上[^n]标记。 从技术层面看,需要重点解释三个维度:首先是基础定义,tcpi_snd_cwnd即发送方拥塞窗口的当前值,单位是MSS;其次是动态机制,它如何随AIMD算法变化;最后是实践价值,开发者如何通过TCP_INFO选项获取它来优化程序。用户引用的iperf代码片段正好可以作为应用案例。 注意到用户引用的材料里有段有趣的细节:tcpi_snd_cwnd实际对应内核tp->snd_cwnd变量,而真正的发送速率还受pacing机制限制。这点需要强调,避免用户误以为拥塞窗口直接等于发送速率。可以补充说明实际带宽公式:$实际带宽 = \frac{cwnd \times MSS}{RTT}$。 最后生成的问题要围绕TCP调试场景,比如如何获取cwnd实时值、典型优化手段等。用户明显更关注实践而非理论,所以问题要偏向工具使用而非算法推导。</think>`tcpi_snd_cwnd` 是 Linux 内核通过 `TCP_INFO` socket 选项提供的一个关键网络参数,用于**实时反映当前 TCP 连接的发送端拥塞窗口大小**。其名称是 **T**CP **I**nfo **S**e**n**d **C**ongestion **W**in**d**ow 的缩写。 ### 技术含义 1. **拥塞窗口 (Congestion Window, cwnd)**: * 这是 TCP 协议的核心机制之一,用于控制发送方在**未收到接收方确认(ACK)** 的情况下,最多能向网络注入多少数据包。 * 发送方维护 `cwnd` 变量,其值表示当前允许发送的**最大未确认数据段数量**(通常以 MSS 为单位)。 * `cwnd` 的大小动态调整,是 TCP **拥塞控制算法**(如 Cubic, Reno, BBR 等)的核心输出。算法根据网络状况(是否检测到丢包、延迟变化等)增大或减小 `cwnd`。 2. **`tcpi_snd_cwnd` 的作用**: * 它提供了**用户空间程序**一个接口,使其能够查询内核中当前为该特定 TCP socket 维护的 `cwnd` 值。 * 其值是一个**无符号整数 (`uint32_t`)**,表示当前拥塞窗口的大小,**单位是发送方的最大报文段长度 (MSS - Maximum Segment Size)**。 * 例如,如果 `tcpi_snd_cwnd = 10` 且 `tcpi_snd_mss = 1460` (字节),则意味着发送方当前最多允许有 10 个 MSS 的数据(即 10 * 1460 = 14600 字节)在传输中且未被确认。 ### 技术用途 获取 `tcpi_snd_cwnd` 的主要目的是**监控、诊断和优化 TCP 连接的性能**: 1. **实时性能监控**: * 监控 `cwnd` 的波动可以直接反映网络拥塞状况的变化。`cwnd` 突然下降通常表明检测到丢包(网络拥塞信号),而 `cwnd` 的稳定增长则表明网络状况良好。 * 结合其他 `tcp_info` 字段(如 `tcpi_rtt` - 往返时间、`tcpi_snd_ssthresh` - 慢启动阈值、`tcpi_lost` - 丢失报文数等),可以构建详细的 TCP 连接健康状态视图。 2. **带宽估算**: * 在稳定状态下,TCP 连接的**理论最大瞬时发送速率**可以通过 `cwnd` 和 RTT 估算:$速率 \approx \frac{cwnd \times MSS}{RTT}$。 * 例如,`cwnd=20`, `MSS=1460`, `RTT=50ms`,则理论速率约为 $\frac{20 \times 1460 \times 8}{0.05} = 4,672,000$ bps 或约 4.67 Mbps。注意这是瞬时上限,实际速率受接收方窗口、应用层发送速度、Pacing 限制等影响[^3]。 3. **拥塞控制算法验证与调优**: * 开发者或网络研究人员可以通过观察 `tcpi_snd_cwnd` 的变化模式,验证特定拥塞控制算法(如 BBR, Cubic)在实际网络环境中的行为是否符合预期。 * 帮助诊断算法参数配置是否合理。 4. **应用层优化**: * 高性能网络应用(如视频流服务器、大规模分布式系统、网络测量工具如 iperf)可以利用 `tcpi_snd_cwnd` 信息进行智能决策。 * 例如: * 自适应调整应用层发送策略:当 `cwnd` 很小(表明网络拥塞)时,应用可以主动降低发送速率或切换备用路径。 * 更准确地预测传输完成时间。 * 在网络测量工具中报告连接的实际瓶颈(是拥塞窗口限制还是接收窗口限制?)。 5. **网络诊断与故障排除**: * 如果连接性能低下,持续观察到的 `tcpi_snd_cwnd` 值远小于预期(例如,远低于 `tcpi_snd_ssthresh` 或无法增长),可能是网络路径存在严重丢包、缓冲区膨胀(Bufferbloat)导致高延迟,或者对端接收窗口过小的问题。 * 帮助区分问题是出在本地发送端、网络路径还是远端接收端。 ### 如何获取 在 Linux 系统上,可以通过 `getsockopt` 系统调用并指定 `TCP_INFO` 选项来获取包含 `tcpi_snd_cwnd` 字段的 `struct tcp_info` 结构体。如引用[2][^2]和引用[4][^4]所示: ```c #include <netinet/tcp.h> // 定义 struct tcp_info ... struct tcp_info tcpi; socklen_t len = sizeof(tcpi); if (getsockopt(sockfd, IPPROTO_TCP, TCP_INFO, &tcpi, &len) == 0) { printf("Current congestion window (cwnd): %u packets (MSS units)\n", tcpi.tcpi_snd_cwnd); // 也可以获取其他信息如:tcpi.tcpi_snd_mss, tcpi.tcpi_rtt, tcpi.tcpi_snd_ssthresh 等 } ``` ### 重要注意事项 * **非标准/非可移植**:如引用[1][^1]所述,`TCP_INFO` 及其返回的 `struct tcp_info` 是 Linux 特有的扩展,不同内核版本结构体字段可能增减或含义变化。代码依赖此接口将**不具备可移植性**。 * **瞬时快照**:`tcpi_snd_cwnd` 的值是调用 `getsockopt` 时刻内核中该 socket 状态的一个**瞬时快照**。TCP 拥塞窗口变化非常频繁(收到每个 ACK 都可能变化),因此连续两次获取的值可能不同。 * **单位**:牢记其单位是 **MSS**(`tcpi_snd_mss`),不是字节。计算字节大小需要 `tcpi_snd_cwnd * tcpi_snd_mss`。 * **实际速率限制**:`tcpi_snd_cwnd` 限制了 *in-flight* 数据量,但实际发送速率还受内核 Pacing 机制(如 BBR 或 `SO_MAX_PACING_RATE`)、接收方通告窗口 (`rwnd`)、应用层发送速度等因素制约。实际速率可能低于 $\frac{cwnd \times MSS}{RTT}$[^3]。 ### 总结 `tcpi_snd_cwnd` 是 Linux 提供的用于实时查询 TCP 发送端拥塞窗口大小的关键指标。它以 MSS 为单位,直接反映了 TCP 拥塞控制算法对当前网络状况的判断结果。其主要用途集中在**网络性能监控、带宽估算、拥塞控制算法研究、应用层传输优化以及网络问题诊断**等领域,是理解和优化 TCP 连接性能的重要工具,尽管它属于 Linux 特有的非可移植接口。 [^1]: `TCP_INFO` (since Linux 2.4) Used to collect information about this socket. The kernel returns a struct tcp_info... This option should not be used in code intended to be portable. [^2]: 其中`getTcpInfo`使用了`getsockopt(sockfd_,SOL_TCP,TCP_INFO,tcpi,&len)`;`TCP_INFO`我没有在man手册找到,在头文件`/usr/include/netinet/tcp.h`中找到了定义。 [^3]: 一. send_bps 根据RTT和拥塞窗口计算的发送速率... `send_bps = (double) info->tcpi_snd_cwnd * (double)info->tcpi_snd_mss * 8000000. / rtt;` ... 实际速率可能受Pacing check、接收窗口限制等影响而低于此值。 [^4]: iperf 示例代码:通过`getsockopt(sp->socket, IPPROTO_TCP, TCP_INFO, (void *)&irp->tcpInfo, &tcp_info_length)`获取`tcp_info`,并打印`tcpi_snd_cwnd`等字段。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值