在互联网出现的早期,为了保证交换机buffer不会过饱和,会在MAC层进行流量控制,如果当前节点的接受速率大于发送速率,则会在接收端的MAC层主动丢包,避免网络拥堵。
? 但是,现如今,网络中数据包的转发节点愈来愈多,MAC层的流量控制无法满足多节点转发的情况,因此,流量控制的工作也就自然而然的交给了更高层的协议,也就是现在的传输层。
? 传输层的流量控制是通过TCP的滑动窗口协议实现的(过去的拥塞控制一直是通过TCP来实现,因为UDP本身没有确认机制,但是近几年出现了一种新的基于UDP的拥塞控制——QUICK,具体原理我没细看,此处暂不涉及UDP拥塞控制,只谈TCP),当发送窗口内还有未发送的数据时,就继续向网络发送;当窗口内所以数据都已发送且没有收到ACK时,就停止发送等待回复。这就实现了端到端的流量控制。
? 但这里涉及到一个问题,那就是,发送窗口的大小要怎么确定?如果发送窗口很大,那么可能应用层传下来的数据甚至可能都填不满发送窗口,发送端会一直持续满载向网络发送数据包,哪怕当前网络已经非常拥堵导致出现了大量的丢包,发送端也不会停下来等待。而如果发送窗口太小,当窗口内最后一份数据发送完后,第一份数据的ACK都还没收到,此时发送端只能停止发送,坐在那干等ACK,浪费大量带宽。
? 由此可见,对于窗口大小的控制,就成了在“尽量避免网络拥堵”的前提下“充分利用带宽”的关键技术,而这个关键技术,就是我们今天要讲的“拥塞控制”。就我个人的理解而言,拥塞控制相当于是一种“动态流量控制“,根据当前网络拥堵状况,实时的调整流量的阈值。
? 拥塞控制的基本原则就是:当网络中没有出现拥塞,拥塞窗口的值就可以再增大一些,以便把更多的数据包发送出去;当网络出现拥塞,拥塞窗口的值就应该减小一些,以减少注入到网络中的数据包数。
? 传统的拥塞控制,无论是Tahoe、Reno、New-Reno,还是2000年后出现的BIC、CUBIC等,本质上都是基于“丢包”来判断网络的拥塞状况,通过AIMD四种算法的结合,动态的对发送窗口进行调整。但是,基于丢包的控制有一个巨大的缺陷,那就是Bufferbloat问题。
? 早期,互联网上的路由器缓存都较小,当节点拥堵时,路由器缓冲区很快就会被填满,从而引发丢包。因此,发送端可以较快的接收到网络拥堵信息,从而快速对拥塞窗口进行调整。
? 但是,随着硬件成本的快速下降,路由器缓冲区越来越大,当节点拥堵时,新到达的数据会源源不断的塞进缓冲区而不发生丢包,这就导致了“高延迟,低丢包”网络的出现,而由于此时对于发送端来说没有发生丢包,就不会去降低发送速率,进而使这种高延迟的状态持续下去,这就是上述提及的Bufferbloat问题,该现象极大的影响了用户体验。
Bufferbloat现象如下图所示,在发送方的发送速率超过网络中瓶颈节点的最大速率后,链路中的缓冲区持续被填充,此时发送方感受到的RTT值持续增加,但是接收方收到的数据速率却维持在一个固定的值,此时若发送方继续增大发送速率,不仅不会提高DeliveryRate,反而会提高通讯延迟。
? 为了缓解Bufferbloat问题,基于时延的拥塞算法思想逐渐走入人们的视野,其中比较典型的就是Vegas算法。Vegas算法是基于rtt的变化率来判断网络拥塞状况的,其通过更为精确的采样机制(每个ack都会进行速率采样),通过采样数据结构rate_sample保存的时间,来实时更新rtt值,并和当前的拥塞窗口值snd_cwnd进行除法运算,计算当前时间间隔(RTT)内的局部最大发送速率v_local与tcp连接的全局最大发送速率v_overall,通过对比v_local与v_overall的差异大小,来决定增/减窗。
? 这种基于rtt变化的算法,一旦遇到延时的大幅增加,就会快速降低发送速率,从而迅速排空链路上的缓冲队列,极大的缓解了Bufferbloat问题。但是,由于互联网上的tcp链接,基本都在使用基于丢包的拥塞协议,当基于时延的拥塞协议因为Bufferbloat而降低发送速率时,基于丢包的协议依然会保持高速发送,从而将前者让出的带宽迅速蚕食,因此,基于时延的拥塞算法在基于丢包的算法面前毫无竞争力,无法适应当前的网络环境。
? 理论上而言,当互联网中都使用基于rtt的拥塞算法时,因为所有人都可以快速的感知到拥塞而迅速调整速率,从而极大的降低拥塞发生的概率和严重程度,降低了协议中的惩罚机制所带来的带宽损失。但这毕竟是理论,现实中的互联网发展了这么多年,有数百亿的网络终端,要让这数百亿终端同时更换底层网络协议几乎是不可能的,因此,像Vegas这种基于rtt的拥塞协议,一直得不到大规模的应用,直到BBR的出现。