刚看了会儿关于流体模型和排队论的东西,觉得太高端了,但实际能做的事情又很少,有感而发,写篇随笔。
TCP的拥塞控制远不止Linux内核源码树的net/ipv4目录下的那些,事实上那些算法误导了算法的实现者。
当我想要基于Linux实现一个自己的拥塞控制算法的时候,我感到无助,因为TCP的拥塞状态机是被内置在TCP核心中的,这意味着我无法在算法中全权接管对拥塞窗口的控制。
说到底,这个拥塞状态机完全是1988年范雅格布森那一套设计的,实际上这个状态机的外骨架就是Reno,虽然慢启动和拥塞避免可以在算法中被定制,但快速重传,快速恢复,超时是TCP核心控制的,对这个过程的控制,外部无能为力。
BBR算法的引入,这个拥塞状态机开放了很多,但依然限制很多,比方说我想写一个测试算法,无论如何将cwnd保持在常量1000000,然而在拥塞状态机的某些状态,这个cwnd将无法保持。
这些事实让写一个真实用起来的拥塞控制算法是一件非常麻烦的事,为了全权控制拥塞窗口,几乎要重写tcp_ack,而这件事与TCP无关,从开始重写tcp_ack到用起来的时间取决于你对Linux内核HOOK技术的熟练程度。
现在离Reno时代已经很久了,正确的拥塞控制,应该将丢包,重传,窗口控制完全分开:
- 拥塞控制算法全权控制拥塞窗口。
- 拥塞状态机仅仅标记丢包。
- 正常传输和重传统一处理。
对于拥塞控制算法,其输入是TCP的当前状态,输出则是一个拥塞窗口,系统的其它任何地方都不要有拥塞窗口的输出。类似ssthresh,慢启动,拥塞避免,快速重传这类Reno概念也是可以完全取消的。
事实上,拥塞控制状态机是根本不用对丢包进行反应的,这基于下面的事实:
- 如果拥塞控制算法是好的,那么算法本身将会计算一个恰好的窗口,避免丢包。
- 如果是线路噪声造成的丢包,那更与拥塞无关。
说完了Linux TCP拥塞控制的实现问题,再说下拥塞控制算法本身。
看了Sprout后,你会有些不一样的想法,不妨感受一下:
https://www.usenix.org/system/files/conference/nsdi13/nsdi13-final113.pdf
虽说Sprout是一个双边的方案,但与以往各种算法不同的是,它是基于分布的,确保90%的情况下,数据在queue排队时间不超过10ms,所有的计算均以逼近此约束为目标,在连续的计算结果允许的窗口内发送数据。这才是真正的拥塞控制,和吞吐,时延抖动,丢包恢复完全无关。
事实上,吞吐的测量,时延抖动,丢包等等均是判定拥塞的手段,各种算法均以这类手段来判断是否发生了拥塞,从而调整拥塞窗口,包括BBR也不例外。
Sprout直接以控制拥塞为目标(显然,保持数据包排队时间维持在10ms内,就相当于控制了拥塞),并且保持向目标逼近。那么目标从哪里来?目标可以是算出来的,也可以是对端反馈的。显然,对于Sprout而言,目标是对端反馈的。
现在我们来看另一个类似的,也是直接向目标逼近的算法,但这次并没有对端反馈,需要自己算,这是一个单边算法,即Remy。
Remy以在合理分配带宽的约束下最优的带宽利用率为目标,然后用一个简陋的机器学习过程逼近这个目标,虽然简陋但却五脏俱全:
https://github.com/tcpexmachina/remy
很多人把好的TCP拥塞控制算法当作TCP加速的措施,或者反过来。但这是不对的,他们忽略公平性。
从Reno时代开始直到BBR,所有拥塞控制算法的核心目标都是公平而不是效率,你会看到Reno只是一个兜底的收敛算法,对带宽利用率豪无保证,甚至50%都达不到,所有基于AIMD的算法都是无法兼顾效率的。BBR可以说是第一个以提高带宽利用率的MIMD算法,但以为它是MIMD,因此无法兼顾公平性,上周我写过一篇分析:
https://blog.csdn.net/dog250/article/details/118627070
Sprout我没有实际分析,但Remy不同,从Remy的算法过程可以看出它是完全兼顾公平和效率的,我想部署实测下,结果又回到了本文开头的框架问题,作罢!
浙江温州皮鞋湿,下雨进水不会胖。