linux内核工程导论-网络:tcp拥塞控制

Posted broler

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux内核工程导论-网络:tcp拥塞控制相关的知识,希望对你有一定的参考价值。

这篇文章本来是在tcp那篇里面的,但是那篇太长了,不专一。就完善了一下提取出来了。

TCP拥塞控制

       拥塞控制讨论的是很多个同时存在的tcp连接应该怎么规划自己的数据包发送和接收速度,以在彼此之间共享带宽,同时与其他实体的机器公平的竞争带宽,而不是自己全占。
       拥塞控制的核心是AIMD(additive-increase/multiplicative-decrease ),线性增加乘性减少。为啥不用线性增加线性减少,或者是乘性增加乘性减少呢?这个有人专门研究过,只有AIMD可以收敛聚合使得链路公平。具体的过程我也不清楚,只知道结论。

拥塞检测

窗口(每一段应当补例子)

       由于sequence机制天然的具有对网络拥塞的感知能力。因此感知到拥塞后如何反应处理就是应该考虑的问题了。这部分有些偏数学,所以学术界很喜欢研究这个问题。自然的,相对应的拥塞控制算法就数不胜数。
       目前最广泛被接受的思想和算法是这样的:基于网络速度突然发生急剧变化的概率小,拥塞是逐渐发生的(人看来仍然很快,但机器看来是有个过程的)。而在这个过程中数据是逐渐变得不可达。因此,双方应该有学习机制。TCP留下了用于实现这个的域:窗口。这个窗口也是代表内存接收缓存的大小,缓存本身就有缓冲作用,因此即使是突然变化的网络,在缓存中也是有一个过程。正常的通信缓存不会满,但是一方发送了很多数据(这些数据直到确认前都在发送缓存中),却没有被确认,那么可用的发送缓存就越来越小,所以其发送量就开始收缩。同样的,接收方知道自己的接收缓存的大小,也就是知道自己的接收能力。所以其也会及时的告知发送方允许对方发送的最高速度。意思是,我现在能一次接收N个字节,你一次性发我,不要多,也最好不要少。
       这个缓存还有一个很重要的功能就是乱序重组。接收到的数据包放在缓存中,收齐了才会返回给上层,也正是如此,接收缓存如果在不稳定的网络中很容易被填满。
       总结一下:接收缓存的作用是规定最大接收速度和乱序重排。发送缓存的作用是丢包重传和控制发送速度。
       TCP规定的这个窗口与缓存的大小有关,与确认对方发送数据的sequence组合,表示的就是接下来可以接收的数据序号区间。窗口只用来告知对方自己的接收能力,不用来表达自己的发送能力。这个发送能力需要根据对方的接收能力和当前自己对信道的估计自己进行调整。核心的思想就是你不需要再数据包中告诉自己该怎么操作,你应该建议对方如何。
       通过窗口检测并控制数据流量的主要算法有:

RTT

       判断是否拥塞,不但可以通过窗口检测拥塞,还可以通过往返时间的直接测量。窗口检测是观察,往返时间属于测量。最早的拥塞控制算法Vegas就是根据RTT来监测和控制的。但是由于RTT不是根据实际的丢包率来计算的,而是根据往返时间,而互联网,尤其是无线网,RTT变大并不意味着不可达或者拥塞,这时使用Vegas算法的就开始主动降低自己的速度(因为它判断网络拥塞了),而其他的基于丢包率的算法则并不减小窗口。导致Vegas为人民服务,把可用带宽让给别人了。这种损己利他的做法和nagle一样,是注定要消亡的。
但是RTT仍然对拥塞控制有至关重要的作用(除了后来的完全不依赖rtt的cubic算法),大部分算法的都是在收到ack回复的时候才把窗口增加一个MSS。这就是慢启动的部分(但是增长速度相当快)的原理。

拥塞避免

       治乱于未乱是最合理的治安方法。害怕发生拥塞首先要想办法避免拥塞。要避免拥塞就要分析拥塞发生的原因,无疑是网络传输问题。然而事情并没有那么简单,是什么导致了网络传输的拥塞呢?你可能不假思索的说是传输的数据太多。然而大多并不是这种业务上的问题,而是技术上的问题。
       这里的拥塞避免有两个维度的术语。一个是我们不了解细节技术时候认为的避免拥塞。另外一个是技术上的慢启动算法。整个拥塞控制过程包含了慢启动(窗口指数增长)/拥塞避免过程(窗口线性增长)/发生了拥塞的处理这三个过程(后两个从技术上讲是一样的)。

带宽节省

       很自然的,避免拥塞的最好方法就是别发那么多数据。但是这对于用户来说是不现实的,毕竟传输数据是网络存在的根本意义。但是内核还是尽可能的从技术上减少用户的数据。典型的就是nagle算法。

Nagle

       TCP传输的数据有两种:命令和数据。TCP明不知道它的流中是命令还是数据。命令的特点是短,而且需要立即响应。数据的特点是长,可以多接收一些再进行响应。针对数据,一般吞吐量较大,一般立即发送即可,因为上层每次提交到内核要发送的内容就不少。但是针对命令,如果每次都是立即发送,则本可以合并在一起发送的数据包被拆成了多个,使得发送同样的数据占用了更多的带宽。为此,linux设计了nagle算法。
       Nagle算法规定发送了一个包出去,一直要等到该包的回复才会发送第二个包,在此过程中,数据在发送缓存中缓存累积。如此就可以将尽量多的命令数据合并节省上行带宽。但是这个算法的初衷是美好的,但是效果是可悲的,更可悲的是还是默认打开的。因为,nagle算法对带宽的节省是通过对自己发出的命令的延时进行了,超时还是得立即发送。但是就是这个延时让自己的应用程序感受到系统响应的缓慢。而且这个缓慢还是自己给别人节省上行带宽(发送命令一般占不了自己的多少带宽)造成的。这个时候只要自己关闭掉nagle算法,就会发现自己的传输响应明显加快。典型的应用是samba如果关闭nagle,tcp的传输速度一般会提高。Nagle这种舍己为人的算法设计在市场中是不得人心的,但是初衷是好的。

拥塞控制

       我们能检测到拥塞,我们也得避免拥塞。现代的所有拥塞避免算法都是基于4个核心概念展开的:慢启动、拥塞避免和快速重传、快速恢复。这4个基本算法是由Reno拥塞避免算法首先提出的,后来在TCP NewReno中又对“快速恢复”算法进行了改进,近些年又出现了选择性应答( selective acknowledgement,SACK)算法,还有其他方面的大大小小的改进,成为网络研究的一个热点。

慢开始(慢启动)与拥塞避免

       接收方永远通报自己的窗口,例如300个字节。发送方根据接收方发来的窗口计算出自己在当前窗口下可以发送的数据序号,例如从200-500,共300个,接下来可以任性的发送,不需要等待ack。假设在接收端回复了下一次窗口为500,接收确认的是400序号,则发送端计算接下来可以任性发送的序号是400-900,共500个字节。如此周而复始。
       慢开始算法就是:当新建连接时,cwnd初始化为1个最大报文段(MSS)大小(或者整数倍,linux默认是65535),发送端开始按照拥塞窗口大小发送数据,每当有一个报文段被确认,cwnd就增加1个MSS大小。这样cwnd的值就随着网络往返时间(Round Trip Time,RTT)呈指数级增长,事实上,慢启动的速度一点也不慢,只是它的起点比较低一点而已。
       上面的情况是接收方窗口在不断的增大,这种情况一般会在所有的TCP连接建立初期发生。由于两个节点建立TCP连接的时候并不知道链路的质量,所以发送端也不好确认一下子可以任性的发送多少数据出去,所以作为一个对信道的探测,TCP连接建立的初期,接收端一般会把窗口设的很小,然后成倍的增大。这叫做慢启动。增大到一定的值后就会慢速增加(线性),是一个逐渐适应的学习过程。从指数增加切换到线性增加的阈值叫做Slow Start Threshold (SSThresh)。很多算法就在这个值上做文章(例如动态的变化这个值)
       进入的窗口值慢速增加的过程就是拥塞避免的过程。因为刚开始的时候通常不会发生拥塞,这个时候慢开始设置的初始窗口太小,为了快速到达最大速度,窗口是指数增加的,但是到了某一个设定的阈值,增加窗口就得线性增加以防止过快的发生拥塞(快到极限的时候慢速试探),这个线性增加窗口的过程就是拥塞避免的过程。这个设定的阈值叫做慢启动门限 (SSThresh)。
       在慢启动阶段如果发生了拥塞,接收方就缩小自己的接收窗口至1(或者是系统的默认值65535),如此发送方就不会发送那么多的数据,重新还是执行慢开始算法。这个算法是TCP Tahoe。而TCP Reno的做法是在慢启动的过程中当检测到拥塞时,把慢启动门限SSThresh设置为当前窗口的一半,所以就会强制立即进入拥塞避免阶段。

慢开始的缺陷

       慢开始与拥塞避免使基于一个常识性的假设:收到报文了说明网络质量有提高的空间,而发送端要推测有多大提高空间呢?这就与rtt和当前的窗口大小相关。所以这个算法是在收到确认后才增加。而在丢包比较严重的环境,比如wifi,其实带宽是有很大的,只是丢包重,这时候这种依据ack确认的机制表现并不好,增长会比较慢,并且比较容易被误认为是带宽不够。也就是说发送端无法区别网络拥塞与链路质量(两者带来的效果都是丢包)。
       并且由于是慢开始,刚开始的时候增长速度虽然很快,但是基数毕竟很小,所以这种对于短连接的效果非常差。人们不是试图为短连接采用更好的算法,而是基于现在大家都使用的这种算法想了很多对策。比如同时开多条tcp连接,或者是尽量的复用同一条连接。但是比如web server这种服务就无法正确的被满足了。所以你会发现,现在的流量器打开一个网站的时候会使用很多个tcp连接去下载不同的资源。

快速恢复与快速重传(如何减少窗口的调整)

       窗口增大的过程是拥塞避免的过程,窗口减小的过程是拥塞控制的过程。拥塞控制就是在检测到发生了拥塞(或可能的拥塞),通信双方的反应情况。拥塞窗口的调整可以实现拥塞控制。前面说的是慢开始算法的一部分。

快速重传

       除了增大窗口,还有减小窗口的情况。减小一般是剧烈的。在检测到网络中发生了拥塞之后(收不到数据),接收到就会缩小自己的接收窗口,但是怎么缩小就是各个算法不同的了。所谓的AIMD算法就是在这里发挥作用的。AIMD的加性增长就是指冲突避免的过程的窗口线性增大,而乘性减少就是发生了拥塞之后的规避机制(没发生拥塞之前窗口一直在增大,直到物理的缓存内存不够放)。快速重传就是避免减小窗口而导致窗口波动的算法。
       网络是不可靠的,这个不可靠在发送和接收两端的表现是收到重复的包和没有收到包。在TCP中,收到重复的包会导致困惑的是发送方收到重复的ack。其可能认为是网络问题,也可能是接收方一直没有收到某个数据而发送的重复的ack。TCP没有为这种情况设计额外的机制好让接收方可以在每次发送同样的ack时在数据包上有区别。这就给发送方带来了困扰。传统的收到多个ack发送方会认为是网络重传,直到其tcp超时机制启动发现之前发送的包超时没有被回复ack,发送方才判断多个ack是自己发送的包接收方没有收到,而不是发送方收到网络原因重复的包。快速恢复算法就是在收到3个(或4个,看实现)重复的ack时就判断做出是因为自己发送的包丢失的结论,从而判断网络拥塞发生。一判断拥塞发生就启动重传,但是这时候的重传并不像慢开始算法,将窗口回到1,重现开始执行算法。这时候很大概率只是偶然的网络丢失,所以其只是重发丢失的包,按照原来的速度继续发送。这叫快重传。这种机制可以显著的降低错误的收缩窗口的概率。
       由此可见TCP机制设计的精巧,堪称异步问题解决的典范。这种设计也是完全建立在物理网络的特性的基础之上的。因为现在的网络是尽力而为的网络。当需求超出其负荷时,其反应是降低服务质量,而不是限制接入数量。如此的网络设计,就让工作在其上的所有协议都要考虑丢包和重传的问题。

快速恢复

快重传与快恢复是一个算法,但是由于历史原因,说起来像两个算法的名字。他们分别描述该算法的两个过程:快速恢复与快速重传。快重传成功了,自然就快速恢复了。

其他服务于快速恢复算法的机制
SACK

       SACK(选择性确认)是TCP选项,它使得接收方能告诉发送方哪些报文段丢失,哪些报文段重传了,哪些报文段已经提前收到等信息。
根据这些信息TCP就可以只重传哪些真正丢失的报文段。需要注意的是只有收到失序的分组时才会可能会发送SACK,TCP的ACK还是建立在累积确认的基础上的。也就是说如果收到的报文段与期望收到的报文段的序号相同就会发送累积的ACK,SACK只是针对失序到达的报文段的。

D-SACK

       重复的SACK。RFC2883中对SACK进行了扩展。SACK中的信息描述的是收到的报文段,这些报文段可能是正常接收的,也可能是重复接收的,通过对SACK进行扩展,D-SACK可以在SACK选项中描述它重复收到的报文段。但是需要注意的是D-SACK只用于报告接收端收到的最后一
个报文与已经接收了的报文的重复部分

FACK

       FACK(提前确认)算法采取激进策略,将所有SACK的未确认区间当做丢失段。虽然这种策略通常带来更佳的网络性能,但是过于激进,因为SACK未确认的区间段可能只是发送了重排,而并非丢失。

实际的拥塞控制算法实现

       前面是理论基础,但是实现上要考虑不同的东西,设计不同的参数比例。而且网络不断的出现新的情况,需要针对性的进行调整。近几年来,随着高带宽延时网络(High Bandwidth-Delay product network)的普及,针对提高TCP带宽利用率这一点上,又涌现出许多新的基于丢包反馈的TCP协议改进,这其中包括HSTCP、STCP、BIC-TCP、CUBIC和H-TCP。现在cubic是linux使用最多的拥塞控制算法,ubuntu的默认算法。这里有一个常见的算法列表:
这里写图片描述
       总的来说,基于丢包反馈的协议是一种被动式的拥塞控制机制,其依据网络中的丢包事件来做网络拥塞判断。即便网络中的负载很高时,只要没有产生拥塞丢包,协议就不会主动降低自己的发送速度。这种协议可以最大程度的利用网络剩余带宽,提高吞吐量。然而,由于基于丢包反馈协议在网络近饱和状态下所表现出来的侵略性,一方面大大提高了网络的带宽利用率;但另一方面,对于基于丢包反馈的拥塞控制协议来说,大大提高网络利用率同时意味着下一次拥塞丢包事件为期不远了,所以这些协议在提高网络带宽利用率的同时也间接加大了网络的丢包率,造成整个网络的抖动性加剧。这种算法就相当于明知道网络要饱和了,但是还没有饱和的余量我要来占用,所以只有某个人用会占尽便宜,但是大家都用就会过快的饱和。TCP拥塞控制是个典型的个人利益最大化集体利益最小化的博弈过程。
       BIC-TCP、HSTCP、STCP等基于丢包反馈的协议在大大提高了自身吞吐率的同时,也严重影响了其他流的吞吐率。基于丢包反馈的协议产生如此低劣的TCP友好性的组要原因在于这些协议算法本身的侵略性拥塞窗口管理机制,这些协议通常认为网络只要没有产生丢包就一定存在多余的带宽,从而不断提高自己的发送速率。其发送速率从时间的宏观角度上来看呈现出一种凹形的发展趋势,越接近网络带宽的峰值发送速率增长得越快。这不仅带来了大量拥塞丢包,同时也恶意吞并了网络中其它共存流的带宽资源,造成整个网络的公平性下降。但是也正是因为没有更高的权威,所以这种算法注定要成为通用的算法。其实我也认为,拥塞避免不应该是server自己的事情,而应该是路由器qos的事情。我倾向于所有的tcp都直接不要拥塞控制,窗口上来就设置为内存支持的最大大小就好了,能不能发成功,是否拥塞,交给路由器去烦心吧。自己只需要检测丢包率,太高的时候才降低自己的窗口。
       技术上,这些算法不过都是对Reno算法在窗口何时以何种幅度增大减小的控制策略上的改变。从表格中可以看到,除了机遇丢包反馈的,还有基于延时和综合的还有信号的(不太讨论范围)。由于现在是机遇Loss的统一天下了,这里只讨论这种。

HSTCP(High Speed TCP)

       HSTCP(高速传输控制协议)是高速网络中基于AIMD(加性增长和乘性减少)的一种新的拥塞控制算法,它能在高速度和大时延的网络中更有效地提高网络的吞吐率。它通过对标准TCP拥塞避免算法的增加和减少参数进行修改,从而实现了窗口的快速增长和慢速减少,使得窗口保持在一个足够大的范围,以充分利用带宽,它在高速网络中能够获得比TCP Reno高得多的带宽,但是它存在很严重的RTT不公平性。公平性指共享同一网络瓶颈的多个流之间占有的网络资源相等。
       TCP发送端通过网络所期望的丢包率来动态调整HSTCP拥塞窗口的增量函数。
       拥塞避免时的窗口增长方式: cwnd = cwnd + a(cwnd) / cwnd
       丢包后窗口下降方式:cwnd = (1-b(cwnd))*cwnd
       其中,a(cwnd)和b(cwnd)为两个函数,在标准TCP中,a(cwnd)=1,b(cwnd)=0.5,为了达到TCP的友好性,在窗口较低的情况下,也就是说在非BDP的网络环境下,HSTCP采用的是和标准TCP相同的a和b来保证两者之间的友好性。当窗口较大时(临界值LowWindow=38),采取新的a和b来达到高吞吐的要求。具体可以看RFC3649文档。

westwood

       无线网络中,在大量研究的基础上发现tcpwestwood是一种较理想的算法,它的主要思想是通过在发送端持续不断的检测ack的到达速率来进行带宽估计,当拥塞发生时用带宽估计值来调整拥塞窗口和慢启动阈值,采用aiad(additive increase and adaptive decrease)拥塞控制机制。它不仅提高了无线网络的吞吐量,而且具有良好的公平性和与现行网络的互操作性。存在的问题是不能很好的区分传输过程中的拥塞丢包和无线丢包,导致拥塞机制频繁调用。

H-TCP

       高性能网络中综合表现比较优秀的算法是:h-tcp,但它有rtt不公平性和低带宽不友好性等问题。

BIC-TCP

       BIC-TCP的缺点:首先就是抢占性较强,BIC-TCP的增长函数在小链路带宽时延短的情况下比起标准的TCP来抢占性强,它在探测阶段相当于是重新启动一个慢启动算法,而TCP在处于稳定后窗口就是一直是线性增长的,不会再次执行慢启动的过程。其次,BIC-TCP的的窗口控制阶段分为binary search increase、max probing,然后还有Smax和Smin的区分,这几个值增加了算法上的实现难度,同时也对协议性能的分析模型增加了复杂度。在低RTT网络 和低速环境中,BIC可能会过于“积极”,因而人们对BIC进行了进一步的改进,即CUBIC。是Linux在采用CUBIC之前的默认算法。
       但是在长肥管道下,BIC的侵略性是正好的。

CUBIC

       CUBIC在设计上简化了BIC-TCP的窗口调整算法,在BIC-TCP的窗口调整中会出现一个凹和凸(这里的凹和凸指的是数学意义上的凹和凸,凹函数/凸函数)的增长曲线,CUBIC使用了一个三次函数(即一个立方函数),在三次函数曲线中同样存在一个凹和凸的部分,该曲线形状和BIC-TCP的曲线图十分相似,于是该部分取代BIC-TCP的增长曲线。另外,CUBIC中最关键的点在于它的窗口增长函数仅仅取决于连续的两次拥塞事件的时间间隔值,从而窗口增长完全独立于网络的时延RTT,之前讲述过的HSTCP存在严重的RTT不公平性,而CUBIC的RTT独立性质使得CUBIC能够在多条共享瓶颈链路的TCP连接之间保持良好的RTT公平性。

STCP,Scalable tcp。

       STCP算法是由 Tom Kelly于 2003年提出的 ,通过修改 TCP的窗口增加和减少参数来调整发送窗口大小 ,以适应高速网络的环境。该算法具有很高的链路利用率和稳定性,但该机制窗口增加和 RTT成反比 ,在一定的程度上存在着 RTT不公平现象 ,而且和传统 TCP流共存时 ,过分占用带宽 ,其 TCP友好性也较差。

TCP Proportional Rate Reduction

       这是压轴的主角,内核3.2之后默认使用这个算法。不过rfc上写的是试用期的。

以上是关于linux内核工程导论-网络:tcp拥塞控制的主要内容,如果未能解决你的问题,请参考以下文章

TCP慢启动,拥塞控制,ECN 笔记

TCP拥塞控制

计算机网络——拥塞控制

常见的tcp拥塞控制有哪几种算法

在TCP的拥塞控制中,啥是慢开始、拥塞避免、快重传和快恢复算法

TCP Congestion Control