TCP-拥塞控制

本文主要讨论TCP实现拥塞控制的方法,这也是批量数据传输中最重要的。web

前面咱们讨论了能够经过滑动窗口来作流控,可是TCP认为这还不够,由于滑动窗口须要依赖于链接的发送端和接收端,其并不知道网络中间发生了什么。TCP的设计者以为,一个伟大而牛逼的协议仅仅作到流控并不够,由于流控只是网络模型4层以上的事,TCP的还应该更聪明地知道整个网络上的事。算法

具体一点,咱们知道TCP经过一个timer采样了RTT并计算RTO,可是,若是网络上的延时忽然增长,那么,TCP对这个事作出的应对只有重传数据,可是,重传会致使网络的负担更重,因而会致使更大的延迟以及更多的丢包,因而,这个状况就会进入恶性循环被不断地放大。试想一下,若是一个网络内有成千上万的TCP链接都这么行事,那么立刻就会造成“网络风暴”,TCP这个协议就会拖垮整个网络。这是一个灾难。网络

因此,TCP不能忽略网络上发生的事情,而无脑地一个劲地重发数据,对网络形成更大的伤害。对此TCP的设计理念是:TCP不是一个自私的协议,当拥塞发生的时候,要作自我牺牲。就像交通阻塞同样,每一个车都应该把路让出来,而不要再去抢路了。ssh

TCP针对网络拥塞的场景有四种主要的算法:1)慢启动,2)拥塞避免,3)拥塞发生时的快速重传,4)快速恢复。svg

慢启动Slow start

TCP慢启动的意思是,刚刚加入网络的链接,一点一点地提速,不要一上来就像那些特权车同样霸道地把路占满。新同窗上高速仍是要慢一点,不要把已经在高速上的秩序给搞乱了。网站

慢启动的算法过程以下(cwnd全称Congestion Window):
1)链接建好的开始先初始化cwnd = 1,代表能够传一个MSS大小的数据。google

2)每当收到一个ACK,cwnd++; 呈线性上升设计

3)每当过了一个RTT,cwnd = cwnd*2; 呈指数让升xml

4)还有一个ssthresh(slow start threshold),是一个上限,当cwnd >= ssthresh时,就会进入“拥塞避免算法”(后面会说这个算法)blog

因此,咱们能够看到,若是网速很快的话,ACK也会返回得快,RTT也会短,那么,这个慢启动就一点也不慢。下图说明了这个过程。
这里写图片描述

这里,我须要提一下的是一篇Google的论文《An Argument for Increasing TCP’s Initial Congestion Window》Linux 3.0后采用了这篇论文的建议——把cwnd 初始化成了 10个MSS。 而Linux 3.0之前,好比2.6,Linux采用了RFC3390,cwnd是跟MSS的值来变的,若是MSS< 1095,则cwnd = 4;若是MSS>2190,则cwnd=2;其它状况下,则是3。

拥塞避免算法–Congestion Avoidance

前面说过,还有一个ssthresh(slow start threshold),是一个上限,当cwnd >= ssthresh时,就会进入“拥塞避免算法”。通常来讲ssthresh的值是65535,单位是字节,当cwnd达到这个值时后,算法以下:

1)收到一个ACK时,cwnd = cwnd + 1/cwnd

2)当每过一个RTT时,cwnd = cwnd + 1

这样就能够避免增加过快致使网络拥塞,慢慢的增长调整到网络的最佳值。很明显,是一个线性上升的算法。

快速重传(拥塞态时的算法)

前面咱们说过,当丢包的时候,会有两种状况:

1)基于重传计时器:等到RTO超时,重传数据包。TCP认为这种状况太糟糕,反应也很强烈。

  • sshthresh = cwnd /2
  • cwnd 重置为 1
  • 进入慢启动过程

2)快速重传(Fast Retransmit)算法,也就是在收到3个duplicate ACK时就开启重传,而不用等到RTO超时。

  • TCP Tahoe的实现和RTO超时同样。
  • TCP Reno的实现是:
    a) cwnd = cwnd /2
    b)sshthresh = cwnd
    c)进入快速恢复算法——Fast Recovery

上面咱们能够看到RTO超时后,sshthresh会变成cwnd的一半,这意味着,若是cwnd<=sshthresh时出现的丢包,那么TCP的sshthresh就会减了一半,而后等cwnd又很快地以指数级增涨爬到这个地方时,就会成慢慢的线性增涨。咱们能够看到,TCP是怎么经过这种强烈地震荡快速而当心得找到网站流量的平衡点的。

快速恢复

TCP Reno
这个算法定义在RFC5681。快速重传和快速恢复算法通常同时使用。快速恢复算法是认为,你还有3个Duplicated Acks说明网络也不那么糟糕,因此没有必要像RTO超时那么强烈。 注意,正如前面所说,进入Fast Recovery以前,cwnd 和 sshthresh已被更新:

  • cwnd = cwnd /2
  • sshthresh = cwnd

而后,真正的Fast Recovery算法以下:

  • cwnd = sshthresh + 3 * MSS (3的意思是确认有3个数据包被收到了)
  • 重传Duplicated ACKs指定的数据包
  • 若是再收到 duplicated Acks,那么cwnd = cwnd +1
  • 若是收到了新的Ack,那么,cwnd = sshthresh ,而后就进入了拥塞避免的算法了。

若是你仔细思考一下上面的这个算法,你就会知道,上面这个算法也有问题,那就是——它依赖于3个重复的Acks。注意,3个重复的Acks并不表明只丢了一个数据包,颇有多是丢了好多包。但这个算法只会重传一个,而剩下的那些包只能等到RTO超时,因而,进入了恶梦模式——超时一个窗口就减半一下,多个超时会超成TCP的传输速度呈级数降低,并且也不会触发Fast Recovery算法了。

一般来讲,正如咱们前面所说的,SACK或D-SACK的方法可让Fast Recovery或Sender在作决定时更聪明一些,可是并非全部的TCP的实现都支持SACK(SACK须要两端都支持),因此,须要一个没有SACK的解决方案。而经过SACK进行拥塞控制的算法是FACK(后面会讲)

TCP New Reno
因而,1995年,TCP New Reno(参见 RFC 6582 )算法提出来,主要就是在没有SACK的支持下改进Fast Recovery算法的——

  • 当sender这边收到了3个Duplicated Acks,进入Fast Retransimit模式,开发重传重复Acks指示的那个包。若是只有这一个包丢了,那么,重传这个包后回来的Ack会把整个已经被sender传输出去的数据ack回来。若是没有的话,说明有多个包丢了。咱们叫这个ACK为Partial ACK。
  • 一旦Sender这边发现了Partial ACK出现,那么,sender就能够推理出来有多个包被丢了,因而乎继续重传sliding window里未被ack的第一个包。直到再也收不到了Partial Ack,才真正结束Fast Recovery这个过程

咱们能够看到,这个“Fast Recovery的变动”是一个很是激进的玩法,他同时延长了Fast Retransmit和Fast Recovery的过程。

算法示意图

下面咱们来看一个简单的图示以同时看一下上面的各类算法的样子:
这里写图片描述