web性能之TCP的构成(笔记)

291 阅读14分钟

概况

因特网有两个核心协议:IP(Internet Protocol 因特网协议)和TCP(Transmission Control Protocol 传输控制协议)。TCP/IP也常被称为“因特网协议套件”(Internet Protocol Suite)。

  • IP负责联网主机之间的路由选择和寻址;
  • TCP负责在不可靠的传输信道之上提供可靠的抽象层。向应用层隐藏了大多数网络通信的复杂细节。比如丢包重发、按序发送、拥塞控制及避免、数据完整等等。

HTTP标准并未规定TCP是唯一的传输协议。如果愿意,还可以通过UDP(用户数据包协议)或者其它协议来发送HTTP消息。由于TCP提供了很多有用的功能,几乎所有的HTTP流量都是通过TCP传送的。

三次握手

所有TCP连接都需要经过三次握手

  • SYN
    客户端选择一个随机序列号J,并发送一个SYN分组(包),其中可能还包括其它TCP标志和选项。进入SYN—SENT状态,等待服务器确认。
    SYN:同步序列编号(Synchronize Sequence Numbers)
  • SYN ACK
    服务器给J+1,并选择自己的一个随机序列号K,追加自己的标志和选项,然后返回响应,即SYN+ACK包,此时服务器进入SYN_RECV状态
  • ACK
    客户端给K+1,并发送握手期间最后一个ACK分组,此包发送完毕,客户端和服务端进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

tip: 客户端可以在发送ACK分组之后立即发送数据,而服务端必须等接收到ACK分组之后才能发送数据。

三次握手带来的延迟使得每创建一个新TCP连接都要付出很大的代价。而这也决定了提高TCP应用性能的关键,在于想办法重用连接,例如TFO(TCP Fast Open)

TCP Fast Open (tcp快速打开)

假定客户端在此前的TCP连接中已完成请求Fast Open Cookie的过程并存有有效的Fast Open Cookie:

  • 客户端发送SYN数据包,该数据包包含数据(对于非TFO的普通TCP握手过程,SYN数据包不包含数据)以及此前记录的Cookie;
  • 支持TCP Fast Open的服务器会收到Cookie 进行校验:如果Cookie有效,服务器将在SYN-ACK数据包中对SYN和数据进行确认,服务器随后将数据递送至响应的程序;否则,服务器将丢弃SYN数据包中包含的数据,且其随后发出的SYN-ACK数据包将仅确认SYN的对应序列号;
  • 如果服务器接受了SYN数据包中的数据,服务器可在握手完成之前发送数据;
  • 客户端将发送ACK确认服务器发回的SYN以及数据,但如果客户端在初始的SYN数据包中发送的数据未被确认,则客户端将重新发送数据;
  • 此后的TCP连接和非TFO的正常情况一致。

限制:随同SYN分组一起发送的数据净荷有最大尺寸的限制、只能发送某些类型的HTTP请求,以及由于依赖加密cookie,只能应用于重复的连接

拥塞预防和控制

网络拥塞是指在分组交换网络中传送分组的数目太多时,由于存储转发节点的资源有限而造成网络传输性能下降的情况。

网络拥塞是一种持续过载的网络状态,此时用户对网络资源(包括链路带宽、存储空间和处理器处理能力等)的需求超过了固有的处理能力和容量。

  • 存储空间限制
    在每个输出端口有一定的存储空间,若一个输出端口被几个输入数据流共同使用,输入流的数据包就会在该存储空间内排队等待输出。当端口转发数据的速率低于数据包的到达速率时,会造成存储空间被占满的情形,后到达的数据包将被丢弃。突发数据流的此种现象更多。虽然从某种程度上来讲,存储空间的增加能够缓解输出端口的压力。但是存储空间无限制的增加,会导致数据包完成转发时,它们早已超时,源端认为这些数据包在传输过程中被丢弃而要求重发,不仅降低网络效率,而且使得网络拥塞情况更加严重。另外在实际应用中,存储容量不可能无限制的增加,不符合实际意义。
  • 带宽容量的限制
    通过实践证明低速链路难以应对高速数据流的输入,从而发生网络拥塞。依据香农理论,信源的发送速率必须小于或等于信道容量。因此,当源端带宽远大于链路带宽形成带宽瓶颈时,导致数据包在网络节点排队等待,造成网络拥塞。
  • 处理器性能限制
    路由器中的 CPU 主要执行缓存区排队、更新路由表、进行路由选择等功能,如果其工作效率不能满足高速链路的需求,就会造成网络拥塞。

流量控制

流量控制是一种预防发送端过多向接收端发送数据的机制。否则,接收端可能因为忙碌、负载过重或缓冲区既定而无法处理。为了实现流量控制,TCP连接每一方都要再通告自己的接收窗口(rwnd),其中包换能够保存数据的缓冲区空间大小信息。

第一次建立连接时,两端都会使用自身系统的默认设置来发送rwnd。浏览网页通常从服务器向客户端下载数据,因此客户端窗口更可能成为瓶颈。上传图片或视频,客户端向服务器传送大量数据,服务器的接收窗口更可能成为瓶颈。

如果其中一端跟不上数据传输,那它可以向发送端通告一个较小的窗口。假如窗口为零,则意味着必须由应用先清空缓冲区,才能接收剩余数据。这个过程贯穿TCP连接的整个生命周期:每个ACK分组都会携带响应的最新rwnd值,以便两端动态调整数据流速,使之适应发送端和接收端的容量及处理能力。

窗口缩放(TCP Window Scaling)

最初的TCP规范分配给通告窗口大小的字段是16位,相当于设定了发送端和接收端窗口的最大值(2^16=65535字节)。这个限制通常无法获得最优性能,特别是“带宽延迟积”很高的网络中。可以通过如下命令检查和启用窗口缩放:

sysctl net.ipv4.tcp_window_scaling sysctl -w net.ipv4.tcp_window_scaling=1

慢启动

流量控制确实可以防止发送端向接收端过多发送数据。但却没有机制预防任何一端向潜在的网络过多发送数据。也就是说,发送端和接收端再连接建立之初,谁也不知道带宽是多少,因此需要一个估算机制,然后还要根据网络中不断变化的条件而动态改变速度。

根据交换数据来估算客户端与服务器之间的可用带宽是唯一的方法,而且这也是慢启动算法的设计思路。首先,服务器通过TCP连接初始化一个新的拥塞窗口(cwnd)变量,将其值设置为一个系统设定的保守值。

  • 拥塞窗口大小
    发送端对从客户端接收确认(ACK)之前可以发送数据量的限制。

发送端不会通告cwnd变量,即发送端和接收端不交换这个值。客户端与服务端之间最大可以传输(未经ACK确认的)数据量取rwnd和cwnd变量中的最小值。那么服务器和客户端怎么确定拥塞窗口最优值呢?解决方案是慢启动,即在分组被确认后增大窗口大小,即在分组被确认后增大窗口大小,慢慢的启动。最初,cwnd值只有1个TCP段,1999年增加到了4个TCP段,2013年增加到10个TCP段。

慢启动对构建浏览器应用非常重要,因为包括HTTP在内的很多应用层协议都运行在TCP之上,无论带宽多大,每个TCP连接都必须经过慢启动阶段,我们不可能一上来就完全利用连接的最大带宽。

我们从一个较小的拥塞窗口开始,每次往返令其翻倍(指数式增长)。而达到某个目标吞吐量所需的时间,就是客户端与服务器之间往返时间(RTT)和初始拥塞窗口大小的函数。

cwnd大小达到N所需要的时间:
时间 = 往返时间 * log2(N/初始 cwnd);

重用TCP连接,可以减少慢启动时间。

MSS:报文段

  • 通常在刚刚开始发送报文段时,先把拥塞窗口 cwnd 设置为一个最大报文段MSS的数值。而在每收到一个对新的报文段的确认后,把拥塞窗口增加至多一个MSS的数值。用这样的方法逐步增大发送方的拥塞窗口 cwnd ,可以使分组注入到网络的速率更加合理

如果不施加手段进行控制,慢启动必然使得cwnd很快膨胀,为防止拥塞窗口cwnd的增长引起网络拥塞,还需要另外一个变量,慢开始门限ssthresh

  • cwnd <ssthresh时,进行慢开始算法。
  • cwnd>ssthresh时,进行拥塞避免算。
  • cwnd = ssthresh时,两者皆可

拥塞避免算法:

  • 让拥塞窗口cwnd缓慢地增大,即每经过一个往返时间RTT就把发送方的cwnd 拥塞窗口cwnd加1cwnd ,而不是加倍cwnd 。这样拥塞窗口cwnd按线性规律缓慢增长,比慢开始算法的拥塞窗口增长速率缓慢得多。
  • 不论是慢开始还是拥塞避免只要网络出现拥塞(没有按时到达)时,就把ssthresh的值置为出现拥塞时的拥塞窗口的一半(但不能小于2),以及cwnd置为1,进行慢开始。 目的是迅速减少主机发送到网络中的分组数,使得发生 拥塞的路由器有足够时间把队列中积压的分组处理完毕。

快速恢复和快速重传

在很多情况下,发送端都有可能接收到重复的确认报文段,比如TCP报文端丢失,或者接收端收到乱序TCP报文段并重排之等。

拥塞控制算法需要判断当收到重复的确认报文端时,网络是否真的发生了阻塞,或者说TCP报文端是否真的丢失了。具体的做法是:发送端如果连续收到3个重复的确认报文端,就认为是拥塞发生了。然后它启用快速重传和快速恢复算法来处理拥塞。

快速重传(Fast retransmit):要求接收方在收到一个失序的报文段后就立即发出重复确认(为的是使发送方及早知道有报文段没有到达对方),而不要等到自己发送数据时捎带确认。

如果在超时重传定时器溢出之前,接收到连续的三个重复冗余ACK(其实是收到4个同样的ACK,第一个是正常的,后三个才是冗余的),发送端便知晓哪个报文段在传输过程中丢失了,于是重发该报文段,不需要等待超时重传定时器溢出,大大提高了效率。这便是快速重传机制。

快速恢复(Fast retransmit)具体过程:

  1. 当发送方连续收到三个重复确认,就执行“乘法减小”算法,把慢开始门限ssthresh减半。这是为了预防网络发生拥塞。然后立即重传丢失的报文段,并将CWND设置为新的ssthresh(减半后的ssthresh)
    请注意:接下去不执行慢开始算法

有些快重传实现是把开始时的拥塞窗口cwnd值再增大一点,即等于 ssthresh + 3 * MSS 。这样做的理由是:既然发送方收到三个重复的确认,就表明有三个分组已经离开了网络。这三个分组不再消耗网络 的资源而是停留在接收方的缓存中。可见现在网络中并不是堆积了分组而是减少了三个分组。因此可以适当把拥塞窗口扩大了些。

  1. 由于发送方现在认为网络很可能没有发生拥塞,因此与慢开始不同之处是现在不执行慢开始算法(即拥塞窗口cwnd现在不设置为1),而是把cwnd值设置为慢开始门限ssthresh减半后的数值,然后开始执行拥塞避免算法(“加法增大”),使拥塞窗口缓慢地线性增大。

  2. 每次收到一个重复的确认时,设置CWND=CWND+SMSS(拥塞窗口加1).此时发送端可以发送新的TCP报文段

  3. 当收到新数据的确认时,设置CWND=ssthresh(ssthresh是新的慢启动门限值,由第一步计算得到) 原因是因为该ACK确认了新的数据,说明从重复ACK时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态。

快速重传和快速恢复完成之后,拥塞控制将恢复到拥塞避免阶段.

慢启动重启

除了调节新连接的传输速度,TCP还实现了SSR(Slow-Start Restart)慢启动重启机制。,这种机制会在连接空闲一定时间后重置连接的拥塞窗口。在连接空闲时,网络状况也可能发生变化,为了避免拥塞,灵应将拥塞窗口重置回“安全的”默认值。

很明显,对那些突发空闲的长周期TCP连接(比如http的keep-alive连接)有很大影响。建议服务器上禁用SSR:

sysctl net.ipv4.tcp_slow_start_after_idle sysctl -w net.ipv4.tcp_slow_start_after_idle=0

拥塞预防

TCP调节性能主要依赖丢包反馈机制非常重要。慢启动以保守的窗口初始化连接,随后每次往返都会成倍提高传输的数据量,知道超过接收端的流量控制窗口,即系统配置的拥塞阈值窗口,或者分组丢失为止,此时拥塞预防算法介入。

拥塞预防算法把丢包作为网络拥塞的标志,即路径中某个连接或路由器已经拥堵了,以至于采取删包措施,因此,必须调整窗口大小,以避免造成更多的包丢失,以保持网络畅通。

重置窗口后,拥塞预防机制会按照自己的算法来增大窗口以尽力那个避免丢包。某个时刻,可能又会有包丢失,于是这个过程从头开始。如此往复,就能发现TCP连接的跟踪曲线,为什么是呈现锯齿状的了。这是拥塞控制和预防算法再调整拥塞窗口,进而消除网络中的丢包问题。

带宽延迟积

数据链路的容量与端到端延迟的乘积。也即是任何时刻处于在途未确认状态的最大容量。

队首阻塞

性能调优

  • 把服务器的内核升级到最新版本(Linux: 3.2+);
  • 确保cwnd大小为10;(即增大TCP初始拥塞窗口)
  • 禁用空闲后的慢启动(慢启动重启);
  • 确保启用窗口缩放;
  • 减少传输冗余数据;
  • 压缩要传输的数据;
  • 把服务器放到离用户最近的地方以减少往返时间;
  • 尽最大可能重用已经建立的TCP连接;(如TCP快速打开)

参考来源: