几种TCP拥塞控制算法的分析
拥塞控制算法是实现TCP的重要组件,目前已有非常多的TCP Congestion Control Algorithm. 不同的算法有自己的优化特性和工作区域。首先,本文简单介绍一下TCP拥塞避免算法的工作原理;其次,介绍Reno, Vegas, Veno, Westwood 和Cubic的算法实现;最后,本文简单分析tcp_boost算法的工作框架。
1. TCP拥塞避免算法的工作原理
在不考虑FACK(selective ACK)的情况下[注1],tcp congestion control一般包含三个阶段: slow start, congestion avoidance 和fast recovery. 通俗的说,slow start是指数级增长窗口(snd_cwnd),直到slow start threshold (ssthresh) ,congestion avoidance是加性增长窗口直到丢包(3 dupACK, 进入fast recovery)或者超时(超过RTO, 进入slow start), fast recovery是在丢包后,重设snd_cwnd与ssthresh. 其工作状态机如图1所示:
图1:TCP拥塞控制状态机
2. Reno, Vegas, Veno, Westwood 与Cubic
Reno是基本的AIMD(加性增乘性减算法),在slow start阶段指数增长窗口,snd_cwnd = snd_cwnd + 1 (#per ack), 在congestion avoidance阶段线性增长窗口, snd_cwnd = snd_cwnd + 1/snd_cwnd (#per ack)。进入fast recovery后,ssthresh = snd_cwnd/2, snd_cwnd = ssthress(+ 3)[注2]。
Vegas在slow start阶段仍然是指数增长窗口(Reno slow start),在进入congestion avoidance后会做判断。其判断依据是N_que (中间转发设备上的队列大小)。N_que的计算逻辑是,发送窗口snd_cwnd减去带宽(近似值,利用snd_cwnd /RTT_actual来获得)*RTT(无队列时的值, 可使用监测的min RTT)。公式如下:
Vegas的思路是通过调控snd_cwnd来控制N_que的大小,使其满足:
在算法实现时, 时, snd_cwnd ++, 时,snd_cwnd.
Veno是结合Vegas与Reno两种算法的特性来进行拥塞控制。在slow start是Reno slow start 方法。在congestion avoidance阶段,利用N_que来做判断:
在进入fast recovery后,同样利用来做判断:
Westwood算法基于Reno的slow start和congestion avoidance阶段算法,但是其在进入fast recovery后,会根据带宽来做判断。不同于Vegas, Veno 的粗略的带宽计算方法,Westwood利用snd_una (TCP socket中数据已经确认的左边界窗口,注意是其字节值,所以最后需要除去mss_cache)来计算带宽。针对ACK Compression情况,为避免带宽过度波动,做了平滑处理(BWE = alpha * BW(k-1) + (1-alpha) * BW(k))。根据计算后的带宽,在fast recovery做以下赋值:
PS: 在Westwood算法的实现时,有一个小trick。以往的congestion algorithm在设置fast recovery算法时一般是通过设置struct tcp_congestion_ops 里的ssthresh函数。在实际运行中,通过回调该函数求出ssthresh值后,snd_cwnd根据ssthresh来设置新值,一般为ssthresh+3[注2]。而westwood通过hook TCP event的CA_EVENT_COMPLETE_CWR, 来精确的设置snd_cwnd与ssthresh的值。
PS:Westwood是在tcp_ca_event为 CA_EVENT_COMPLETE_CWR时设置的窗口值。这个时候,丢失的包已经重传了,发送窗口已经减半了(其利用Reno ssthresh的方法)。
fast recovery阶段要结束, 即将进入congestion avoidance。因此还是比较迷的,不知道为啥要在这里设置,而不是在ssthresh方法里设置。
Cubic算法是linux默认的tcp congestion avoidance算法。其原理非常简单,利用三次方函数来控制窗口的增长。在slow start阶段,Cubic实现hystart算法,其比Reno slow start 算法的增长速度更快。其在congestion avoidance阶段的增长函数如下所示:
C取值为0.4(在源码的bic_scale设置)。在代码实现时,每次在完成fast recovery后,都会重新设计增长函数(new epoch start, t=0),另求新的K值,公式为
控制snd_cwnd增长速率的手段,是通过设定tcp_cong_avoid_ai( socket, w, acked)里的w值来控制增长速率,使得snd_cwnd = snd_cwnd + 1/w (per ack) ≈snd_cwnd + snd_cwnd/w (per RTT)。在fast recovery阶段,snd_cwnd ≈ 0.7 * snd_cwnd(0.7为源码中的beta/1024)。
注1:实现selective ACK的算法为SACK,但是由于selective ACK需要接受端的TCP协议栈做修改,所以一般的TCP拥塞控制算法不对selective ACK做处理。SACK与Reno ack(非SACK)在进入fast recovery时没有太多区别。FACK会撤销fast recovery,一般客户端实现比较少。
注2:关于fast recovery阶段snd_cwnd最终是如何得到的,整个过程非常复杂。本结论参考《Linux 网络体系结构: Linux内核中网络协议的设计与实现》421页的论述。
注 2:关于fast recovery阶段,可以参考我下一篇博客。