为什么这么设计(Why’s THE Design)是一系列关于计算机领域中程序设计决策的文章,我们在这个系列的每一篇文章中都会提出一个具体的问题并从不同的角度讨论这种设计的优缺点、对具体实现造成的影响。如果你有想要了解的问题,可以在文章下面留言。
TCP 协议可以说是今天互联网的基石,作为可靠的传输协议,在今天几乎所有的数据都会通过 TCP 协议传输,然而 TCP 在设计之初没有考虑到现今复杂的网络环境,当你在地铁上或者火车上被断断续续的网络折磨时,你可能都不知道这一切可能都是 TCP 协议造成的。本文会分析 TCP 协议为什么在弱网环境下有严重的性能问题1。
底层的数据传输协议在设计时必须要对带宽的利用率和通信延迟进行权衡和取舍,所以想要解决实际生产中的全部问题是不可能的,TCP 选择了充分利用带宽,为流量而设计,期望在尽可能短的时间内传输更多的数据2。
在网络通信中,从发送方发出数据开始到收到来自接收方的确认的时间被叫做往返时延(Round-Trop Time,RTT)。
弱网环境是丢包率较高的特殊场景,TCP 在类似场景中的表现很差,当 RTT 为 30ms 时,一旦丢包率达到了 2%,TCP 的吞吐量就会下降 89.9%3,从下面的表中我们可以看出丢包对 TCP 的吞吐量极其显著的影响:
本文将分析在弱网环境下(丢包率高)影响 TCP 性能的三个原因:
TCP 的拥塞控制算法会在丢包时主动降低吞吐量;
TCP 的三次握手增加了数据传输的延迟和额外开销;
TCP 的累计应答机制导致了数据段的传输;
在上述的三个原因中,拥塞控制算法是导致 TCP 在弱网环境下有着较差表现的首要原因,三次握手和累计应答两者的影响依次递减,但是也加剧了 TCP 的性能问题。
拥塞控制
TCP 拥塞控制算法是互联网上主要的拥塞控制措施,它使用一套基于线増积减(Additive increase/multiplicative decrease,AIMD)的网络拥塞控制方法来控制拥塞4,也是造成 TCP 性能问题的主要原因。
第一次发现的互联网拥塞崩溃是在 1986 年,NSFnet 阶段一的骨干网的处理能力从 32,000bit/s 降到了 40bit/s,该骨干网的处理能力直到 1987 和 1988 年,TCP 协议实现了拥塞控制之后才得到解决5。正是因为发生过网络阻塞造成的崩溃,所以 TCP 的拥塞控制算法就认为只要发生了丢包当前网络就发生了拥堵,从这一假设出发,TCP 就使用了慢启动和线增积减6的机制实现拥塞控制。
图 1 - TCP 的拥塞控制机制
每一个 TCP 连接都会维护一个拥塞控制窗口(Congestion Window),拥塞控制窗口的作用有两个:
防止发送方向接收方发送了太多数据,导致接收方无法处理;
防止 TCP 连接的任意一方向网络中发送大量数据,导致网络拥塞崩溃;
除了拥塞窗口大小(cwnd)之外,TCP 连接的双方都有接收窗口大小(rwnd),在 TCP 连接建立之初,发送方和接收方都不清楚对方的接收窗口大小,所以通信双方需要一套动态的估算机制改变数据传输的速度,在 TCP 三次握手期间,通信双方会通过 ACK 消息通知对方自己的接收窗口大小,接收窗口大小一般是带宽延迟乘积(Bandwidth-delay product, BDP)决定的7,不过在这里我们就不展开介绍了。
客户端能够同时传输的最大数据段的数量是接收窗口大小和拥塞窗口大小的最小值,即 min(rwnd, cwnd)
。TCP 连接的初始拥塞窗口大小是一个比较小的值,在 Linux 中是由 TCP_INIT_CWND
定义的8:
C
初始拥塞控制窗口的大小从出现之后被多次修改,几个名为 Increasing TCP’s Initial Window 的 RFC 文档:RFC24149、RFC339010 和 RFC692811 分别增加了 initcwnd
的值以适应不断提高的网络传输速度和带宽。
图 2 - TCP 拥塞控制窗口的线増积减
如上图所示,TCP 连接发送方的拥塞控制窗口大小会根据接收方的响应而变化:
线性增长:当发送方收到了接收方的 ACK 时,拥塞窗口大小会加一;
积式减少:当发送方发送的数据包丢包时,拥塞控制窗口会减半;
如果 TCP 连接刚刚建立,由于 Linux 系统的默认设置,客户端能够同时发送 10 个数据段,假设我们网络的带宽是 10M,RTT 是 40ms,每个数据段的大小是 1460 字节,那么使用 BDP 计算的通信双方窗口大小上限应该是 35,这样才能充分利用网络的带宽:
然而拥塞控制窗口的大小从 10 涨到 35 需要 2RTT 的时间,具体的过程如下:
发送方向接收方发送
initcwnd = 10
个数据段(消耗 0.5RTT);接收方接收到 10 个数据段后向发送方发送 ACK(消耗 0.5RTT);
发送方接收到发送方的 ACK,拥塞控制窗口大小由于 10 个数据段的成功发送 +10,当前拥塞控制窗口大小达到 20;
发送方向接收方发送 20 个数据段(消耗 0.5RTT);
接收方接收到 20 个数据段后向发送方发送 ACK(消耗 0.5RTT);
发送方接收到发送方的 ACK,拥塞控制窗口大小由于 20 个数据段的成功发送 +20,当前拥塞控制窗口大小达到 40;
从 TCP 三次握手建立连接到拥塞控制窗口大小达到假定网络状况的最大值 35 需要 3.5RTT 的时间,即 140ms,这是一个比较长的时间了。
早期互联网的大多数计算设备都通过有线网络连接,出现网络不稳定的可能性也比较低,所以 TCP 协议的设计者认为丢包意味着网络出现拥塞,一旦发生丢包,客户端疯狂重试就可能导致互联网的拥塞崩溃,所以发明了拥塞控制算法来解决该问题。
但是如今的网络环境更加复杂,无线网络的引入导致部分场景下的网络不稳定成了常态,所以丢包并不一定意味着网络拥堵,如果使用更加激进的策略传输数据,在一些场景下会得到更好的效果。
本文转载自 Draveness 技术博客。
原文链接:https://draveness.me/whys-the-design-tcp-performance
评论