在经典的《TCP/IP详解(卷1)》中,作者提到了长肥管道,请先细读相关章节。
请注意,这本书成书已经二十年,当时的带宽是很低的,假设只有16Mbps,RTT为1个单位,带宽换算成字节的话大概就是2MBps,简单地按照十进制换算,记为2000000Bps,我们假设TCP的段长为1000字节,初始拥塞窗口为1个段,那么按照慢启动原理,需要经过多少个RTT才能填满这2MBps的带宽呢?
设需要 R R 个RTT填满带宽,简单的公式如下:
则:
R=log2000000
R
=
log
2
000000
大致计算一下,约21个RTT的时间能将带宽填满,也就是说,在前21个RTT,TCP处于慢启动带宽探测阶段,此时的带宽利用率是非常低的。
设最大BDP为 bdp b d p ,带宽为 B B ,RTT为,则低带宽利用率持续的时间为:
T=log2bdp×t=log2(B×t)×t T = log 2 b d p × t = log 2 ( B × t ) × t
从这个公式中,我们发现带宽 B B 越大,RTT越大,低带宽利用率持续的时间就越久,文件传输的总时间就会越长,这是TCP慢启动的本质决定的,这就是探测的代价。
TCP慢启动这种行为好还是不好,这是一个形而上的问题,本文主要分析一种从这种行为的问题想到的一种优化手段。
如果你想清晰地观测TCP的行为,我想没有什么比tcptrace图更加合适的了吧,想快速了解tcptrace,就实际在wireshark中看一下,然后看看我的一篇文章:
在Wireshark的tcptrace图中看清TCP拥塞控制算法的细节(CUBIC/BBR算法为例):https://blog.csdn.net/dog250/article/details/53227203
接下来我将直接上TCP慢启动的tcptrace图:
先仔细看明白上面的tcptrace图,保证看明白后,故事就要开始了。
周六写了一篇文章:
知乎上的一个问题,TCP in TCP隧道为什么不好:https://blog.csdn.net/dog250/article/details/81257271
顺便喷了一下知乎,紧接着第二天,有网友提出了一个非常典型的超级好的问题贴在了文章的评论里,我原文摘过来,如下:
遇到一个问题,希望作者能指点迷经,不然我也要上知乎提问了。 A -> C 发送100M大文件,tcp连接,用时2.4s。 A -> B -> C 相同文件相同tcp,用时确是1.6s 。经过中间一个节点转发速度是增加的,性能为什么会提升?ABC三个点在物理位置上大约呈直线(重庆,西安,大连)。 相同测试我扩展到4个节点,测试结果用时也是减少的。哪一个环节对是性能提升的关键?测试节点为机房,丢包率几乎没有。
我最初的回答如下:
TCP连接中,物理距离越远,传播延时越大,RTT越大,这意味着窗口打开的越慢,慢启动过程越久。你的物理带宽越大,你所说的问题越明显。中间加一个节点,每一段的RTT就变小了,拥塞窗口在每一段都可以更快的满载。 PS:现在很多跨国SDWAN都采用这种中间终结TCP的方式以减少传输时延。
后来我琢磨着,这么好的一个问题,还是单独总结一篇随笔吧,于是就决定写下本文。
针对这位网友的问题,我觉得没有什么比一张tcptrace图更能解释问题的了,在给出tcptrace图之前,先给出实验拓扑:
然后我们看一下两次实验的tcptrace的对比图。下图分上下两个部分,上半部分表示A到B直接建立TCP连接的慢启动情形,下半部分表示加入了中间节点C后的两个TCP连接接力的情形,A发起的TCP连接在C处终结:
很显然,我们看到了最关键的一环,那就是,加入中间节点B后,A到B的RTT比A直接到C的RTT更小,RTT减小后,包络线往下凸的更加狠了些,也就是说,拥塞窗口的增大速率加快了,窗口打开的速度加快了*,这非常符合文初那简单的公式:
作为理论推导,我们假设没有TCP慢启动过程,节点之间直接用带宽指定的固定的速率发送数据(发送线表现为一条直线而不是慢启动时下凸曲线),会不会节省时间呢?为此,我做一幅图来看个究竟:
并没有!!
看来,通过中间加一个或者几个节点进行TCP中继仅仅在带宽探测过程中可以提高效率。减少RTT可以加快慢启动的过程,进而尽快到达带宽满载的状态,提高总的带宽利用率。
换句话说,为什么大的RTT会导致带宽利用率低,是因为TCP探测带宽是靠ACK反馈计算的,而ACK的采样频率又是和RTT正相关的。RTT越大,ACK采样频率就会越低。
不过,还有trick。我们知道,TCP一旦超时或者应用数据空窗期超过一定的阈值,会从慢启动重新开始,比如TCP的Linux实现中,有下面的参数:
tcp_slow_start_after_idle - BOOLEAN
If set, provide RFC2861 behavior and time out the congestion
window after an idle period. An idle period is defined at
the current RTO. If unset, the congestion window will not
be timed out after an idle period.
Default: 1
一旦发生慢启动的情形,带宽探测将会重新开始,拥塞窗口将会从非常低的值根据ACK反馈增加(tcptrace图上表现为发送线的斜率增加,曲线下凸),此时,加入TCP中继可以促进整个过程快速收敛(tcptrace图上的表现为曲线下凸程度加剧,斜率快速增加),在总体性能上获得良好收益。
显而易见的是,对于那些短连接,比如慢启动尚未结束,传输就终止的连接,TCP中继的加入会提高性能,这是必然的。
非常感谢提出这个典型问题的网友。这个问题不仅仅揭示了一个关于TCP慢启动的原则,更多的是提供了一种TCP加速的方案。
如果一个TCP跨越广阔的物理距离,那么使用传统的TCP拥塞控制方案去优化它的传输性能将会面临巨大的挑战,我们知道TCP是一个端到端的协议,端和端之间越远,物理上的传输时延就是越大,传输的控制就会越难,所以,我们尽可能要减少端和端之间的距离。
没错,CDN就是基于这样的思路,将资源提前运送到里客户端尽可能近的地方,这样就做到了确保数据包的传播延迟尽可能短,而不至于将大量的时间浪费在带宽探测这种慢启动行为上。
然而有个问题,那就是动态资源,并不适合提前运送,那么怎么办?试想下面的拓扑:
Client和Server之间要跨越广袤的地理空间,在TCP/IP网络中观测,则体现为要跨越L1,L2,L3,L4这四段完全不同的链路,RTT非常之长。TCP的端到端特征将会给这种拓扑的拥塞控制带来巨大的挑战,可能L1链路丢包率很低,但是排队延迟大,而L3链路丢包率非常高,L2又是一条限速链路…这将很难实施拥塞控制,链路之间不同的特征会让特定拥塞控制算法的不同策略之间相互掣肘!任何一个地方出现丢包,都有可能将整个TCP连接拉回慢启动状态,带宽利用率非常低。
The fxxking long-RTT,the fxxking end-to-end!
遇到这种问题怎么办?有办法,就是让TCP连接终结于链路的终点处,以链路为单位做中继:
这是一个违背原则的设计,不过这也是一个解决问题的设计,和给人带来问题的原始TCP设计不同,我觉得这个设计可以给人带来收益。
独立的链路独立的拥塞控制,基于每一段链路建立TCP连接,有效缩短总体上浪费在慢启动阶段的时间。但是有个前提,你必须有能力在上图中的特殊位置部署你自己的TCP中继,这显然是个生意,商业噱头….
此外,上述讨论均是在保证中继节点的转发延迟为0的前提下的,显然TCP先终结再开启,这显然是要花费一定的时间的,不过不必担心,主机转发的时间和跨大范围的RTT相比不是一个量级,中国和美国之间的RTT是百毫秒级别,而主机内处理和发送一个数据包则是微秒级别的。唯一值得权衡的就是TCP握手的时延。
接下来我们来看一个细节吧。
还是那个公式:
T=log2bdp×t=log2(B×t)×t T = log 2 b d p × t = log 2 ( B × t ) × t
可以预见,在端到端的TCP被分割的无限小时,RTT,即 t t 无限接近于的时候,则上述的 T T 趋向于0,而RTT增加的时候,不光RTT增加的因素,显然这个因子也在增加,二者相乘,增加的更加猛烈!这对端到端的慢启动是多么莫大的讽刺啊!
再看在最初针对本文的实验描述问题的时候,我给出的那个对比图:
这幅对比图中是假设传输的数据总量是一个定值,显然这是一个慢启动尚未结束就结束传输的案例,要么就是BDP很大,要么就是文件很小。如果文件比较大的情况,假设文件无限大,那么如何在tcptrace图里清晰看出慢启动时间对总下载时间的影响呢?假设RTT为
t
t
,链路固有的带宽为,我们只需要考虑下面的式子:
bdp=B×t b d p = B × t
可见, t t 越小,就越小,一般我们可以将 bdp b d p 看作是最大的in-flight数据包,也就是拥塞窗口的最大值,那么按照慢启动的规则:
cwnd=2R=B×t c w n d = 2 R = B × t
可以看出, t t 越小,就越小,即达到越大带宽所需的时间就越短。我们也能从下面的图示中看出:
超级显然的一个事实,不是吗?
说了这么多,看起来我是在鼓吹一个违背了TCP/IP协议栈原则的设计,我是在怒怼TCP端到端的设计,我竟然说在TCP的两端中间的链路上密密麻麻布满TCP中继,将一个TCP连接拆分成无限多个TCP连接…确实是有点夸张了,但我说的就是这个意思,因为我讨厌TCP,这辈子都会讨厌,好不了了…我比较极端地走到了所有TCP的相反面。TCP是带来问题的,而不是解决问题的。
一开始CPU的指令也是一撸到底执行的,后来才有了流水线,本文说我所描述的TCP中继颇有TCP流水线的意思,画出tcptrace图,你将会看到,第一跳中继和第二跳中继一直到终点,传输是接力的,而控制则是独立的,我昨天做了模拟实验,同样的丢包率,加入一个中继,性能竟然提高了1/3,相当厉害了。TCP中继对于抵抗TCP数据包超时丢包具有非凡的意义!
前面一篇文章中,我说了,TCP in TCP是不好的,即将它们纵向上摞起来,是不好的,在本文中,我表达了,在横向上,TCP back-to-back TCP是好的,我总结一下:
- TCP隧道(TCP in TCP):控制接力(正反馈控制),传输独立(重复传输)。
- TCP中继(TCP back-to-back):传输接力,控制独立。
打翻TCP隧道的同时,请将TCP中继立起来。
PS:我认识的玩SDWAN的老板们已经在玩这些了。
华尔和戈登的故事,正在上演。