Linux 4.9内核的发布,最令人兴奋的特性当属BBR算法了,这个算法的发布让4.9内核简直成了一个里程碑。在4.9内核发布之前,我就详细分析过BBR算法的诸多细节,我的感情色彩从推崇到平淡直到冷漠和咒骂,我个人在刚刚刨去了对BBR的无知而黄袍加身给它的光环后,从头来看,我应该在最初的“推崇”前面加上盲目两个字,是为“盲目推崇”。
在我盲目到一定程度而彻底迷失之前,我仔细研究了与TCP无关但与BBR相关的很多细节,包括骨干网拓扑,时下流行的几款Cisco设备的转发细节(比如Cisco Nexus 95xx系列),以及SDN网络的配置,发现BBR根本就不是一个独立的模块,它完全做不到像CUBIC算法那样的独立收敛,它依赖的外部环境太多太精细,以至于它完全不通用。如果你测试了BBR,并且结果还不错,就像Google的报告中显示的那般,那么恭喜你,你的网络环境与Google的无异,如果你的测试结果不达预期,那么同样恭喜,因为根本没人预期它的表现能有多好,庸人自扰之。Google的测试报告非常严谨,只是说“在….情况下,BBR针对传统TCP的加速比达到…”。
你可知,这个加速比是路由器在摆脱了N加速比之后释放给BBR的额外恩惠,你知道路由器出口排队的N加速比是什么意思吗?我简单点描述好了,路由器的每个出口队列容量必须是其它所有N个入口处理带宽之和的数量级与线速系数的乘积(理论上队列容量要等于入口带宽之和,但考虑到线速转发,队列容量要远远小于这个值),这就是N加速比问题。目前骨干路由器交换机大多数是出口队列,这样N加速比问题就会带来一个很严重的后果,即BufferBloat!我们知道,骨干节点的带宽都是很大的,且节点的出入度都很高,N加速比问题带来的结果就是出口队列非常大(这就是深队列),然而如何保证每个入口只用其中的1/N呢?这就是各式各样的队列规则和调度算法的范畴了,这就是说,在队列规则和调度算法不区分五元组的情况下,深队列缓存很容易被误用!遗憾的是,骨干路由节点很少会关注五元组信息,虽然各种队列管理机制,哈希算法等宗旨都是为了保证“公平”,但是公平的原则是建立在信息量足够的基础之上的。大多数的队列缓存又是共享缓存,所以误用队列缓存的各种流量就非常多了。作为技术洁癖患者,我认为TCP亵渎了IP,你觉得呢?这是我恶心TCP的理由之一。
比如说,由于运营商一般会对UDP流量进行策略化监管或者限速,目前,有一种非常简单的UDP双边加速的方式就是,仅仅将协议号由UDP改成TCP即可!而UDP允许一定程度的丢包,所以说,路由器交换机的AQM对其而言,就是刑不上大夫!
SDN网络或者大型企业骨干网络相比上述的运营商骨干(包括分层的接入,汇聚,核心,跨BGP…),就简单多了。
在SDN网络或者新建的大型企业骨干节点,N加速比问题几乎被排除了。为什么呢?因为这种网络的流量非常容易被调度到一个足够均衡的平衡点。虽然在拓扑复杂性上,骨干网是极其复杂的,然而在流量复杂性上,却又是十分简单,因此控制平面和管理平面可以设计的非常简洁,用以指导数据平面在上述平衡点附近的高效转发。
BBR算法在此处发力!
————————-
可是在你身边又有哪个声称搞TCP的人会去了解上面那些事情呢?
当然,我说这话有点偏见,因为我本身就喜欢上面说的那些路由器呀交换机呀等东西,并且持续了11年,我比较恶心TCP协议以及Socket之类的,并且恶心了11年,所以说在我的字里行间充斥着不公平的诅咒!
————————-
Linux 4.9发布了,大部分并不知所以然的所谓跟风者开始YY了,YY BBR多么多么神奇,多么有望取代CUBIC成为默认拥塞算法…YY它可以超过CUBIC几十倍…那不过是一组你看到的数据,到我这里测试说不定就是渣渣!我上半年也写过一个算法,在我的测试环境下可以秒CUBIC十几倍,可为什么它即便在我个人这里也销声匿迹了呢?
我不喜欢把“场景”这个词用在网络上,我觉得用“属性”这个词更好。场景是可变的,而属性则是固有的。我来简单描述一下BBR适用于什么样子属性的网络。先说结论吧,BBR适用于稳定性比较好的网络。我以道路来做类比,就是两类,一个是高速公路以及城市快速路,一个是展现城市窗口的道路,总之就是秩序感比较强的那种。然而在我们的生活中,80%的时间我们会花在“最后一公里”的道路上,这是一个80/20原则!正是那最后一公里拖慢了整体的效率!
BBR比较适合跑在从一个收费站到另一个收费站之间,或者沿着长安街从东单跑到西单,要么就是沿着世纪大道从陆家嘴跑到世纪公园,一旦拐到小胡同里,就分分钟成渣了。这并没有说BBR不好,就像从来都没有人说兰博基尼不好一样,但是买辆兰博基尼在胡同里开的,要么是为了装逼,要么就是傻逼。
我来根据BBR的状态机画一幅其速度/时间的草图:
一旦发生带宽抖动,BBR将不得不承受“带宽无法充分利用”(带宽向上抖动)或者“保守导致激进”(带宽向下抖动)…不管怎么说,基于内部伺服器的策略都是一厢情愿的轮询做法,它的高效性完全建立在其对整个场景的充分理解基础之上,否则,BBR必须为自己的自行其是付出代价!
温州皮鞋厂老板非常鄙视这种不知其所以然的盲目测试,这一点的认同使得我和温州老板成了朋友。还记得当年评估Intel 82599网卡特性的时候,我告诉过他们,到底是轮询还是中断,完全取决于你对流量属性的感知程度,你要充分测量流量的每一个细节!在这一点上,BBR看样子是做对了!然而,我腻了!
温州老板这么周末要研究下BBR算法,另嘱咐我写一篇关于BBR的科普文章,我本来想答应,但是周末要加班,就只能拖延。我自认为我写一篇可以让任何人看懂的科普性的BBR文章并不太难,我相信自己有这个能力,但是最终,我还是只能拖延,我对温州老板表示抱歉。
从上图可以看到一个值得优化的地方,那就是下面的函数:
- static bool bbr_is_next_cycle_phase(struct sock *sk,
- const struct rate_sample *rs)
- {
- struct tcp_sock *tp = tcp_sk(sk);
- struct bbr *bbr = inet_csk_ca(sk);
- bool is_full_length =
- skb_mstamp_us_delta(&tp->delivered_mstamp, &bbr->cycle_mstamp) >
- bbr->min_rtt_us;
- u32 inflight, bw;
- /* The pacing_gain of 1.0 paces at the estimated bw to try to fully
- * use the pipe without increasing the queue.
- */
- if (bbr->pacing_gain == BBR_UNIT)
- return is_full_length; /* just use wall clock time */
- inflight = rs->prior_in_flight; /* what was in-flight before ACK? */
- bw = bbr_max_bw(sk);
- /* A pacing_gain > 1.0 probes for bw by trying to raise inflight to at
- * least pacing_gain*BDP; this may take more than min_rtt if min_rtt is
- * small (e.g. on a LAN). We do not persist if packets are lost, since
- * a path with small buffers may not hold that much.
- */
- if (bbr->pacing_gain > BBR_UNIT)
- return is_full_length &&
- (rs->losses || /* perhaps pacing_gain*BDP won’t fit */
- inflight >= bbr_target_cwnd(sk, bw, bbr->pacing_gain));
- /* A pacing_gain < 1.0 tries to drain extra queue we added if bw
- * probing didn’t find more bw. If inflight falls to match BDP then we
- * estimate queue is drained; persisting would underutilize the pipe.
- */
- // 如果这里的“||”换成“&&”,将会如何呢?
- return is_full_length ||
- inflight <= bbr_target_cwnd(sk, bw, BBR_UNIT);
- }
就着这个细节,我想来谈一下BBR的抢占性。
BBR的问题在于对带宽变化的不敏感,所以可以看出在上图中两个红色椭圆处会损失带宽,一旦有新的空余带宽,BBR必须等到ProbeMore的增益周期才能发现,而在此之前,可能新的空余带宽已经被别的流抢占了。我们来看一下为什么BBR要在ProbeMore增益周期一次性达到最大的可用带宽,因为BBR在1/4的增益后,新的实际带宽马上就可以在下一个RTT反馈回来,简单点说,BBR的发送速率多了1/4,在一个RTT后,它会发现根据ACK反馈回来的数据计算出的实际带宽真的也增加了1/4,只要能持续收到“美梦成真”的反馈,BBR就会一直继续增速,期待继续可以“美梦成真”!
这一点可以看出,如果在ProbeMore周期,BBR的抢占性是很强的,只要增加的发送速率一直能带来增加的反馈速率,这个过程就一直继续,然而可能其它的数据流并不会等到BBR到达ProbeMore周期就把带宽抢完了!在ProbeBW状态下,ProbeMore只有一次机会,接下来会短暂的ProbeDrain,然后就是6个平稳的发送周期,大约会持续6个RTT的时间,在这段时间内发生的任何事情,BBR都无法感知!会发生什么事情呢?很简单,要么可用带宽增加了,要么可用带宽减少了,如果是减少了,在时间窗口内,以最大的带宽发送数据将会造成丢包,BBR并不识别丢包而降速,所以此时BBR的行为就会比较激进,直到新的ProbeMore之后退出后的ProbeDrain周期!问题在于,收敛到已经减少的可用带宽,需要多个RTT!这又是为什么?
为什么不能像ProbeMore一样,一次性收敛到新的可用带宽呢?
原因很简单,因为不管什么情况,只要你主动降速,ACK马上就能把降速的效果反馈回来,这是100%确定的,没有任何信号告诉你“降速已经够了”!所以说,每轮降速一次就是一个合理的选择,因此可以说,对于BBR而言,实际上是乘性增,加性减的!这种策略用于补偿其对带宽变化的不敏感性!
因此,可以说,BBR的模型是一个探测并使用可用带宽的模型,所依赖是其精细测量出来的rate_sample数据,这不是一个自动收敛的模型,和Reno/CUBIC完全不同。在Reno/CUBIC那里,算法对诸如rate_sample的细节是完全无知的,仅仅依靠ACK Clock就可以驱动算法的运作,最终收敛到一个公平的平衡点。
————————-
我们再看本文中的唯一图示,发现点什么吗?如果你开着一辆车在高速公路上,你会体会到这幅图的深意。
如果你的前车不违规,那么按照跟车距离200米来算,你的大部分时间都处在匀速状态,在不考虑限速120的前提下,你可能会隔一段时间尝试一下更快的速度,但是发现前车并没有提速的情况下,你会慢下来回归原来的速度,宗旨就是保持跟车200米。
然而,如果你在胡同里开车,BBR的原则将完全失效!请参考上图来想象一下你在以下的道路上开车:上海嘉定区叶城路,仓场路,博乐路,深圳宝安区宝安大道,安阳市文峰中路…BBR并不是无级变速,它比较类似与换挡变速机制。加速快,刹车慢。
………
如今,Linux 4.9内核的发布,BBR骚动了全场,然而令人悲哀!太多的连什么是TCP都不懂的人要求测试BBR,这让我感到复杂并且悲伤!
座椅爆炸!经理爆炸!
我的措辞表达了温州老板的内心,我的措辞表达了王姐姐的内心,我在写这些措辞的时候无时无刻不在想着跟小雨对骂的加班的2012年12月29日。
如今,BBR令人躁动不安,把Google的那几个人推向神坛。我由于是国内首批接触BBR的那帮人,在4.9内核发布前就写了几篇文章,很多人给我发邮件,有大量要求加速网络的,有大量要求入职的(甚至包括我现在的公司同事…),有拉私活的,但唯独很少有技术交流的(略有一二,已经加为好友并交谈甚久),敢问你们懂BBR吗?4.9内核一发布,一窝蜂的全出来了,典型的XX拿来主义,XX拿数据说话!
我敢说,BBR无法为你们大多数人带来可观的数据,因为Yuchung Cheng和Neal Cardwell也是揍一拳会趴下的凡人,也不是吃一口屎一口翔香的猛士!但是不可否认,Yuchung Cheng和Neal Cardwell比大多数人更了解自己的网络的属性,更知道SDN的细节,而这些正是TCP程序员所忽略的!如果不了解这些,那就是劣根性带来的悲哀!绝大多数搞TCP的程序员,喜欢掰扯,且根本不懂网络,然而显得装逼,我不屑为伍,以前我是据理力争,然而现在不了。
————————-
Linux 4.9发布了,让很多人躁动了,这让人不安!
对于TCP拥塞控制策略,任何人说任何话都可以自圆其说,就像任何人在任何地点可以评论股市一样!不妄言,甚至闭口不言,宁愿充当哑巴是最明智的,但这并不意味着其它所有人都是智者!
你可以拿BBR数据来炫耀,同时也可以来打脸,但这仅仅是你的数据,如果你不明白上图中的细节,那么,BBR对于你来讲,和CUBIC有区别吗?哈哈!我依然会保持沉默。
———就像何勇在红磡演唱会时抹鼻屎到镜头后说的话一样,我说:我不在乎得罪所有人!
———再次声明,我不在乎高效,我只在乎公平,这是演化论决定的,推荐一本我正在看的书《盲眼钟表匠》。