非常有意思的Flowlet
周五了解到一个叫做Flowlet的东西,比较有意思。大体上说来,它是由一个问题而引出的一个解决方案,由于理解还不够深入,所以也暂时只能这么说。
我先从问题说起。
ISP的动态负载均衡
由于公共骨干网上流量的不确定性,每一条链路的负载不尽相同,为了保证总带宽的最佳利用率,ISP往往会做一些动态负载均衡的策略。如下图所示:
时刻1:
时刻2:
packet负载均衡和flow负载均衡
到底是按照packet做负载均衡呢还是按照一个五元组flow来做负载均衡呢?这是一个问题,很多人在做负载均衡的时候都会面临这个选择问题。
具体来讲,如果一条flow是强序的,那么基于packet的负载均衡将会导致乱序,这是设备在做负载均衡时要避免的,比如TCP就不能基于packet来做负载均衡,而对于UDP这种协议便可以。
目前从主机到中间设备,几乎所有的从板卡,网卡队列,到CPU中断,到hash算法,均有机制保证TCP的强序性。
TCP的问题
正是由于TCP是强序的,所以TCP便无法基于packet做负载均衡,也就意味着,只要一条TCP流已经发起了,它就几乎不能再改变底层链路了,至少是最好不要改变底层链路,因为一旦底层链路改变,TCP将增加面临乱序的概率。
幸亏TCP是burst发送
前面我的描述其实隐含了一个假设,即TCP flow的packet是平滑发送的:
然而实际上,不管是实际抓包(你可以观测抓包的tcptrace图)还是从具体实现(你可以看30年内任何TCP实现的源码,比如cubic,vegas…)上看,你会发现TCP packet的发送其实是burst的:
哈哈,可乘之机!看到两批次burst之间的时间间隙没有,在这种间隙足够大的时候切换下层链路是一个好的时机。这意味着旧链路上的packet均已经离开了链路或者至少将要离开链路,这个时候切换链路将不会造成乱序,不会破坏TCP的强序要求。
嗯,这就是Flowlet。
Flowlet
一般而言,英文中的“-let”后缀代表“微小”的意思,比如booklet,houselet,以及Java applet…一个Flowlet代表的就是一条小流。如下图所示:
在宏观的观感上,可以把一条flow看成是多个flowlet组成的,负载均衡现在就是基于flowlet来进行了,好吧,又引入了一个中间层,它既不是packet,也不是flow,而是大于packet小于flow的flowlet,哈哈,很有意思!
那么到底如何定量的去切分flowlet呢?这里给出一个公式:
建议应该不错了。
BBR之后一切将不再
我个人觉得flowlet的理念非常Q,perfect,通过一个新的层次解决了强序flow的负载均衡问题。然而它借用了一个TCP实现上的问题或者说是bug,即burst机制。所谓借着坏事干好事。
在TCP的AIMD模型下,pacing发送几乎是不可能的,因为pacing的计算会破坏AIMD的盲决策,最终的控制模型将会畸变。然而近期Google的研究证明简单的AIMD用于TCP传输其实是错误的,而引入了一中新的BBR pacing传输模型,这个BBR一下子把TCP拥塞控制算法引入了2.0时代!
我想表达什么?
在BBR pacing模型下,我们假设BBR已经完美升级到了它应该成为的样子,解决了一系列的失速,误判等问题,届时TCP packet的发送将会是下面的样子:
你可能再也捕捉不到那个flowlet中的了,因为pacing rate的精确计算机制不会允许那么久的空窗期存在!
但BBR最终至多只是终结了flowlet在TCP上的具体实现,它无法终结flowlet的理念。拥塞控制和负载均衡是两个不同的领域,虽然有所关联但却井水不犯河水,拥塞控制说的是,当发现拥塞,要怎么做,负载均衡说的是,它可以帮忙分担拥塞。
Linux RFS中的影子
上周写的那篇:
合并N个有序链表与FQ公平调度:https://blog.csdn.net/dog250/article/details/80234049
我找到了一道面试题或者说作业题在Linux中的影子,在我第一次听闻flowlet的昨天,我想Linux RPS/RFS也有该理念的实现,具体看下面这段代码:
/*
* If the desired CPU (where last recvmsg was done) is
* different from current CPU (one in the rx-queue flow
* table entry), switch if one of the following holds:
* ...
* - The current CPU's queue tail has advanced beyond the
* last packet that was enqueued using this table entry.
* This guarantees that all previous packets for the flow
* have been dequeued, thus preserving in order delivery.
*/
if (unlikely(tcpu != next_cpu) &&
(...
((int)(per_cpu(softnet_data, tcpu).input_queue_head -
rflow->last_qtail)) >= 0)) {
tcpu = next_cpu;
rflow = set_rps_cpu(dev, skb, rflow, next_cpu);
}
后记
非常感激总是有人帮助我让我重新思考技术的本质!今天周六一觉睡到8点半,所以这篇文章到了10点多才分享出来,迟来了,但总比没有好。
对flowlet理解不深,不到一日而已,如果有需要讨论的,或者说发现我的说法有错误的,希望能及时指出,我会及时回复。
上午积累了很多的家务事,申请了延后执行,这快到中午了,估计也干不完了,疯子大人和小小马上也就回来了,等待接受批评,但能总结出这篇文章,也算欣慰了。