TCP中的超时时间计算-指数移动加权平均
TCP协议使用指数移动加权平均来计算和预估往返时间RTT。这个东西比较巧妙,比较值得借鉴。
我们假设在某一个时间对往返时间RTT进行一次样本的测量,得到SampleRTT,那么我们对于我们设置均值RTT-EstimatedRTT,可以通过指数移动加权平均来不断迭代:
EstimatedRTT = (1 -a )·EstimatedRTT + a·SampleRTT
其中a就是指数移动加权平均中的“指数”,在[RFC 6298]中给出的a推荐值是a= 0. 125 (即1/8)。
除了估算RTT外,测量RTT的变化也是有价值的。[ RFC 6298]定义了RTT偏差DevRTT, 用于估算SampleRIT一般会偏离EstimatedRTI的程度:
DevRTT = (1 -b)·DevRTT +b*|SampleRTT-EstimatedRTT|
注意到DevRTT是一个SampleRTT与EstimatedRTT之间差值的EWMA。如果SampleRTT值波动较小,那么DevRTT的值就会很小;另一方面,如果波动很大,那么DevRTT的值就会很大。b的推荐值为0.25。
根据往返时间的RTT均值EstimatedRTT,以及我们计算出的波动值DevRTT,可以通过下面这个算式得到往返的超时时间。
在这里,因为超时时间需要略大于预估的往返时间,但是具体大多少呢?大的就是4*波动的预估值,这个计算方法虽然不能说很科学,但是在感性以及工程实践的角度来看,是非常合适的。
TimeoutInterval = EstimatedRTT + 4·DevRTT
TCP中的拥塞控制以及TCP分岔
TCP中通过拥塞控制,来限制发送方的发送速率来达到提高网络质量的目的。
我们简单假设一下,如果发送方的发送速率得不到限制,那么在整条链路上,因为中间路由以及接收方的接受缓存问题,会出现无限的重发以及无限的时延。(因为在接收缓存满的时候,会抛弃掉后续的包,而这又会导致我们“无限制”的发送方继续重发那些必定会丢失的包,这对整条链路以及发送方自己来说,都不是什么好事。
TCP中的拥塞控制主要通过3个状态来控制:
慢启动
TCP的拥塞控制窗口cwnd一般会以一个MSS的大小开始,也就是说一开始只发送1个TCP包(MSS是TCP包数据部分的大小,所以不会太小),此时总速率大约为MSS/RTT,慢启动会让TCP的拥塞窗口cwnd以MSS的指数级别增加,即每次收到一个确认包,MSS就会+1,也就是1->2->4->8的2^增长
拥塞避免
如果慢启动的指数增长时出现了丢包,那么就会进入拥塞避免状态。此时会新引入一个值ssthresh为cwnd的一半,然后将cwnd重新置位1,重新开始慢启动流程
但是因为这次设置了ssthresh,所以再指数增长下的cwnd再次达到或者超过ssthresh时,就会结束慢启动并进入拥塞避免状态
进入到拥塞避免状态后,cwnd开始以线性增长:即每个确认包会使cwnd增加一个MSS的大小
快速恢复
当cwnd持续增长,此时有两种情况的丢包
-基于冗余ACK的丢包,此时会进入快速恢复状态
-时延触发的丢包,此时直接重复拥塞避免状态,cwnd置为1,从新开始线性增加到ssthresh
在快速恢复状态下,因为是冗余ACK行为确认发生的丢包,此时会将cwnd减半,并且当收到3个冗余的ACK,将ssthresh的值记录为cwnd的值的一半。接下来进入快速恢复状态。
在快速恢复中,对于引起TCP进入快速恢复状态的缺失报文段,对收到的每个冗余的ACK, cwnd的值增加一个MSS。最终,当对丢失报文段的一个ACK到达时,TCP在降低cwnd后进入拥塞避免状态。如果出现超时事件,快速恢复在执行如同在慢启动和拥塞避免中相同的动作后,迁移到慢启动状态:当丢包事件出现时,cwnd的值被设置为1个MSS, 并且ssthresh的值设置为cwnd值的一半。
可见基于拥塞控制来说,在不考虑因为时延导致的丢包,TCP的带宽一直在三个状态的循环中反复,在出现拥塞避免后,会一直重复:线性增加->冗余ACK->快速恢复->窗口减半->线性增加,这样的过程,也就是说TCP的带宽窗口大小是锯齿型的
[图,TCP窗口]
TCP分岔
基于拥塞启动的问题,分布式应用或者说部署复杂的应用,多采用TCP分岔的方式来避免慢启动的时延,如果客户端C到服务器S的RTT为500ms,在慢启动的情况下,建立连接的总时延可以认为是4RTT。
但是如果我们在近C端部署一个前置F(CDN就是这样的原理),此时并且F与S之间采用长时间建立连接的方式,那么此时C到S建立连接的总时延就是4RTT(F)+RTT(C),注意因为F是在C的近端网络部署的,所以RTT(F)极小,此时我们的时延缩小了接近4倍