非常有意思的Flowlet

dog250發表於2018-05-12

週五瞭解到一個叫做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呢?這裡給出一個公式:

D1,D2αα滿

已知兩條鏈路的延遲分別為D_1,D_2,設一個數\alpha,當\alpha滿足下面的條件:

α>|D1D2|
\alpha > |D_1-D_2|

flow便αflowlet
一條flow便可以通過\alpha來切割為不同的flowlet

α

\alpha
建議50ms
50ms
應該不錯了。

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中的α

\alpha
,因為pacing rate的精確計算機制不會允許α
\alpha
那麼久的空窗期存在!

  但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理解不深,不到一日而已,如果有需要討論的,或者說發現我的說法有錯誤的,希望能及時指出,我會及時回覆。

  上午積累了很多的家務事,申請了延後執行,這快到中午了,估計也幹不完了,瘋子大人和小小馬上也就回來了,等待接受批評,但能總結出這篇文章,也算欣慰了。

相關文章