流量控制--5.Classless Queuing Disciplines (qdiscs)

charlieroro發表於2020-11-19

Classless Queuing Disciplines (qdiscs)

本文涉及的佇列規則(Qdisc)都可以作為介面上的主qdisc,或作為一個classful qdiscs的葉子類。這些是Linux下使用的基本排程器。預設的排程器為pfifo_fast

6.1 FIFO,先進先出(pfifo和bfifo)

注:雖然FIFO是佇列系統中最簡單的元素之一,但pfifo和bfifo都不是Linux介面上的預設qdisc。參見 Section 6.2, “pfifo_fast, the default Linux qdisc”瞭解更多關於預設qdisc(pfifo_fast)的資訊。

6.1.1 pfifo, bfifo演算法

FIFO演算法是所有Linux網路介面的預設qdisc(pfifo_fast)。它不會對報文進行整流和重排,僅在接收到報文並在報文入佇列後儘快將其傳送出去。這也是新建立的類使用的qdisc(除非使用其他的qdisc或類替換FIFO)。

FIFO演算法維護了一個報文列表,當一個報文入佇列後,會將其插入佇列末尾。當需要將一個報文傳送到網路上時,會將列表首部的報文進行傳送。

6.1.2 limit引數

真實的FIFO qdisc必須限制列表的大小(緩衝大小)來防止溢位,這種情況下無法將接收到的報文入佇列。Linux實現了兩個基本的FIFO qdisc,一個基於位元組數,另一個基於報文。如果不考慮使用的FIFO型別,佇列的大小由引數limit決定。對於pfifo(Packet limited First In, First Out queue),其單位為報文,對於bfifo(Byte limited First In, First Out queue) ,其單位為位元組。

對於pfifo,大小預設等於介面的txqueuelen,可以使用ifconfig或ip檢視。該引數的範圍為[0, UINT32_MAX]。

對於bfifo,大小預設等於txqueuelen乘以介面MTU。該引數的範圍為[0, UINT32_MAX]位元組。在計算報文長度時會考慮鏈路層首部。

例6. 為一個報文FIFO或位元組FIFO指定一個limit

[root@leander]# cat bfifo.tcc
/*
 * make a FIFO on eth0 with 10kbyte queue size
 *
 */

dev eth0 {
    egress {
        fifo (limit 10kB );
    }
}
[root@leander]# tcc < bfifo.tcc
# ================================ Device eth0 ================================

tc qdisc add dev eth0 handle 1:0 root dsmark indices 1 default_index 0
tc qdisc add dev eth0 handle 2:0 parent 1:0 bfifo limit 10240
[root@leander]# cat pfifo.tcc
/*
 * make a FIFO on eth0 with 30 packet queue size
 *
 */

dev eth0 {
    egress {
        fifo (limit 30p );
    }
}
[root@leander]# tcc < pfifo.tcc
# ================================ Device eth0 ================================

tc qdisc add dev eth0 handle 1:0 root dsmark indices 1 default_index 0
tc qdisc add dev eth0 handle 2:0 parent 1:0 pfifo limit 30

6.1.3. tc –s qdisc ls

tc -s qdisc ls的輸出包含limit(報文數或位元組數),以及實際傳送的位元組數和報文數。未傳送和丟棄的報文使用括號括起來,並不計算在Sent之內。

本例中,佇列長度為1000個報文,傳送的681個報文的大小為45894 位元組。沒有丟包,由於pfifo不會降低報文的速率,因此沒有overlimits

$ tc -s qdisc ls dev eth0

    qdisc pfifo 8001: dev eth0 limit 100p
    Sent 45894 bytes 681 pkts (dropped 0, overlimits 0)

如果出現積壓(overlimits),也會顯示出來。

與所有非預設的qdisc一樣,pfifo和bfifo會維護統計資料。

6.2. pfifo_fast, 預設的Linux qdisc

pfifo_fast (three-band first in, first out queue) qdisc是Linux上所有介面使用的預設qdisc。當建立一個介面後,會自動使用pfifo_fast qdisc佇列。如果附加了其他qdisc,這些qdisc則會搶佔預設的pfifo_fast,當移除現有的qdisc時,pfifo_fast會自動恢復執行。

6.2.1. pfifo_fast演算法

該演算法基於傳統的FIFO qdisc,但同時也提供了一些基於優先順序的處理。它使用三個不同的band(獨立的FIFO)來分割流量。具有最高優先順序的流量(互動式流量)會進入band 0,總是會被優先處理。類似地,在band 2出佇列之前,band 1中不會存在未處理的報文。

該演算法與classful prio qdisc非常類似。pfifo_fast qdisc就像三個並排的pfifo佇列,一個報文可以根據其服務型別(ToS)位進入其中某一個FIFO。三個band並不能同時入佇列(當具有最小值,即優先順序高的band包含流量時,具有高數值的,即優先順序低的band就不能出佇列)。這樣可以優先處理互動流量,或者對“最低成本”的流量進行懲罰。每個band最多可以容納txqueuelen 大小的報文,可以使用ifconfigip配置。當接收到額外的報文時,如果特定的band滿了,則會丟棄該報文。

參見下面的6.2.3章節來了解ToS為如何轉換為band。

6.2.2. txqueuelen 引數

三個band的長度取決於介面的txqueuelen

6.2.3. Bugs

終端使用者無需對pfifo_fast qdisc進行配置。下面是預設配置。

priomap決定了報文的優先順序,由核心分配並對映到bands。核心會根據報文的八個位元位的ToS進行對映,ToS如下:

四個ToS位的定義如下:

可以使用 tcpdump -v -v 顯示整個報文的ToS欄位(不僅僅是四個位元位的內容)。

上面展示了很多數值,第二列包含於ToS位元位相關的數值,後面是其對應的意義。例如15表示期望最小貨幣開銷,最大可靠性,最大吞吐量以及最小延遲。

上述四列的內容給出了Linux是如何解析ToS 位元位的,以及它們被對映到的優先順序,如優先順序4對映到的band 號為1。允許對映到更高的優先順序(>7),但這類優先順序與ToS對映無關,表示其他意義。

最後一列給出了預設的優先順序對映的結果。在命令列中,預設的優先順序對映為:

1, 2, 2, 2, 1, 2, 0, 0 , 1, 1, 1, 1, 1, 1, 1, 1

與其他非標準的qdisc不同,pfifo_fast不會維護資訊,且不會展示在tc qdisc ls命令中。這是因為它是預設的qdisc。

6.3. SFQ, 隨機公平佇列

隨機公平佇列是tc命令使用的用於流量控制的classless qdisc。SFQ不會整流,僅負責根據流來排程傳輸的報文,目的是保證公平,這樣每個流都能夠依次傳送資料,防止因為單條流影響了其他流的傳輸速率。SFQ使用一個雜湊函式將流分到不同的FIFO中,使用輪詢的方式出佇列。因為在雜湊函式的選擇上存在不公平的可能性,該函式會週期性地改變,通過擾動(引數perturb)來設定此周變動期性(參見6.3.3引數)。

因此,SFQ qdisc會在任意多的流中,嘗試給每條流分配相同的機會來傳送資料。

6.3.1. SFQ 演算法

在進入佇列之後,會基於報文的雜湊值給每個報文分配一個雜湊桶。該雜湊值可能是從外部的流分類器獲取到的,如果沒有配置外部分類器,則使用預設的內部分類器。當使用內部分類器時,sfq會使用:

  • 源地址
  • 目的地址
  • 源和目的埠

SFQ能夠區分ipv4,ipv6,以及UDP,TCP和ESP等。其他協議的報文會基於32位的目的和源進行雜湊。一條流大多數對應一個TCP/IP連線。

每個桶都應該表示唯一的一條流。由於多條流可能雜湊到同一個桶,sdq的內部雜湊演算法可能會在可配置的時間間隔內受到干擾,但不公平僅會持續很短的一段時間。然而,擾動可能會在無意中導致傳送報文的重排。在Linux 3-3之後,就不會存在報文重排的問題,但可能在重雜湊達到上限(流的數目或每條流的報文數)後丟棄報文。

當出佇列時,會以輪詢的方式請求每個雜湊桶的資料。

在Linux3-3之前,SFQ 的最大長度為128個報文,即最多可以分佈在128個桶(1024個可用桶)上。當發生溢位時,滿的桶會發生尾部丟棄,從而保持公平性。

在Linux3-3之後,SFQ 的最大長度為65535 個報文,除數的極限是65536。當發生溢位時,除非明確要求頭部丟棄,否則滿的桶會發生尾部丟棄,

6.3.2. 命令列使用

tc  qdisc ... [divisor hashtablesize] [limit packets] [perturb seconds] [quantum bytes] [flows number] [depth number] [head drop] [redflowlimit bytes] [min bytes] [max bytes] [avpkt bytes] [burst packets] [probability P] [ecn] [harddrop]

6.3.3. 引數

  • divisor: 可以用於設定不同的雜湊表大小,從2.6.39核心開始可用。指定的除數必須是2的冪,並且不能大於65536。預設為1024。
  • limit: SFQ limit的上限。可以用於減少預設的127個報文長度。在linux-3.3之後,可以增加該值。
  • depth: 每條流的報文限制(linux-3.3之後)。預設為127,可以降低。
  • perturb: 佇列演算法干擾的時間間隔(秒)。預設為 0,意味著不會發生干擾。不要設定過低的值,因為每個干擾都可能導致報文的重排或丟失。建議值為60。當使用外部流分類時,該值將不起作用。為了降低雜湊衝突,可以增加該建議值。
  • quantum: 在輪詢處理期間允許出佇列的位元組數。預設為介面的MTU,這也是建議的最小值。
  • flows: 在linux-3.3之後,可以修改預設的流的限制,預設為127。
  • headdrop: 預設的SFQ行為是尾部丟棄一條流的報文。也可以使用首部丟棄,可以給TCP流提供更好的反饋。
  • redflowlimit: 在每個SFQ流上配置可選的RED模組(用於防止產生bufferbloat問題)。RED(Random Early Detection)的原理是以概率的方式標記或丟棄報文。redflowlimit對每條SFQ流佇列的大小作了硬性限制,單位為位元組。(以下引數為RED的引數)
  • min: 可以進行標記的平均佇列大小,預設為max的三分之一。
  • max: 在此平均佇列大小下,標記的概率最大。預設為 redflowlimit的四分之一。
  • probability: 可以用於標記的最大概率,為一個0.0到1.0的浮點數,預設為0.02。
  • avpkt: 以位元組為單位。與burst一起用於確定平均佇列大小計算的時間常數,預設為1000。
  • burst: 用於確定真實佇列大小對平均佇列大小的影響速度,預設為: (2 * min + max) / (3 * avpkt)。
  • ecn: RED可以執行"標記"或"丟棄 "。顯式的擁塞通知(Explicit Congestion Notification)允許RED通知遠端主機它們的速率超過了可用頻寬。沒有啟用ECN的主機可能會接收到報文丟棄的通知。如果指定了該引數,那麼支援ECN的主機的報文將會被標記,而不會被丟棄(除非佇列滿)。
  • harddrop: 如果平均流佇列長度大於max位元組數,該引數會強制丟棄報文,而不會執行ecn標記。

SFQ中比較容易混淆的是引數:limit,depth,flows這三個引數。limit用於限制SFQ的佇列數目,depth用於限制每條流的數目,flows用於限制流的數目。SFQ會對報文進行雜湊,將雜湊結果相同的報文作為同一條流上的報文,然後將這條流單獨放到一個佇列中(受限於雜湊演算法,有可能存在實際上多個不同的流被雜湊成了SFQ中的同一條流,因此引入了perturb)。

6.3.4 例子和用法

附加到ppp0口。

$ tc qdisc add dev ppp0 root sfq

請注意SFQ和其他非整流的qdisc一樣,僅對其擁有的佇列有效。鏈路速度等於實際可用的頻寬時就是這種情況。 適用於常規電話調變解調器,ISDN連線和直接非交換式乙太網連結。

大多數情況下,有線調變解調器和DSL裝置不屬於這一類。 當連線到交換機並嘗試將資料傳送到同樣連線到該交換機的擁塞段時,情況也是如此。在這種情況下,有效佇列並不在Linux內,因此不能用於排程。在classful qdisc中嵌入SFQ可以確保它擁有該佇列。

可以在sfq中使用外部分類器,例如基於源/目的IP地址對流量進行雜湊:

$ tc filter add ... flow hash keys src,dst perturb 30 divisor 1024

注意給定的divisor應該匹配sfq使用的一個雜湊表。如果修改了sfq的divisor的預設值1024,則流雜湊過濾器也會使用相同的值。

例7. 帶可選RED模組的SFQ

[root@leander]# tc qdisc add dev eth0 parent 1:1 handle 10: sfq limit 3000 flows 512 divisor 16384 redflowlimit 100000 min 8000 max 60000 probability 0.20 ecn headdrop

例8. 建立一個SFQ

[root@leander]# cat sfq.tcc
/*
 * make an SFQ on eth0 with a 10 second perturbation
 *
 */

dev eth0 {
    egress {
        sfq( perturb 10s );
    }
}
[root@leander]# tcc < sfq.tcc
# ================================ Device eth0 ================================

tc qdisc add dev eth0 handle 1:0 root dsmark indices 1 default_index 0
tc qdisc add dev eth0 handle 2:0 parent 1:0 sfq perturb 10

不幸的是,一些聰明的軟體(如Kazaa和eMule等)會通過開啟儘可能多的TCP會話(流)來消除公平排隊帶來的好處。在很多網路中,對於行為良好的使用者,SFQ可以充分地將網路資源分配給競爭的流,但當遭受惡意軟體入侵網路時,可能需要採取其他措施。

可以參見說明文件:man tc-sfq。

SFQ是多佇列演算法,RED是單佇列演算法,可以通過結合兩個演算法來達到更好的流量控制的目的。

6.4. ESFQ, 擴充套件隨機公平佇列

從概念上而言,雖然這類qdisc相比SFQ給使用者提供了更多的引數,但它與SFQ並沒有什麼不同。該qdisc旨在解決上述SFQ的缺點。通過允許使用者控制用於分配網路頻寬的雜湊演算法(hash引數),有可能實現更加公平的頻寬分配。

例9. ESFQ用法

Usage: ... esfq [ perturb SECS ] [ quantum BYTES ] [ depth FLOWS ]
        [ divisor HASHBITS ] [ limit PKTS ] [ hash HASHTYPE]

Where:
HASHTYPE := { classic | src | dst }

6.5. RED,Random Early Drop

隨機早期探測(Random Early Detection)是一種靈活的用於管理佇列大小的classless qdisc。一般的佇列在滿後會從尾部丟棄報文,這種行為有可能不是最優的。RED也會執行尾部丟棄,但是以一種更平緩的方式。

一旦佇列達到特定的平均長度,入佇列的報文會有一定的(可配置)概率會被標記(有可能意味著丟棄該報文),這個概率會線性地增加到某一點,稱為最大平均佇列長度(佇列也可能會變更大)。

相比簡單的尾部丟棄,這樣做有很多好處,且不會佔用大量處理器。這種方式可以避免在流量突增之後導致的同步重傳(這些重傳會導致更多的重傳)。這樣做的目的是使用一個比較小的佇列長度,在資訊的互動的同時不會因為在流量突增之後導致的丟包而干擾TCP/IP流量。

取決於配置的ECN,標記有可能意味著丟棄或僅僅表示該報文是超限的報文。

6.5.2. Algorithm

平均佇列大小用於確定標記的概率,該概率是使用指數加權平均演算法計算出來的,通過該值可以調節對流量突發的敏感度。當平均佇列大小低於最小位元組時,此時不會標記任何報文;當超過最小位元組時,概率會直線上升到probability(引數指定),直到平均佇列大小達到最大位元組數。因為通常不會將概率設定為100%,而佇列大小也可能會超過最大位元組,因此,limit引數用於硬性設定佇列大小的最大值。

大致工作方式為:

  1. 低於min:此時不做任何處理,佇列壓力較小,可以直接正常處理。
  2. 在min和max之間:此時界定為佇列感受到阻塞壓力,開始按照某一機率P從佇列中丟包,機率計算公式為:P = probability * (平均佇列長度 - min)/(max - min)。
  3. 高於max:此時新入隊的請求也將丟棄。

6.5.3. 用法

$ tc  qdisc ... red limit bytes [min bytes] [max bytes] avpkt bytes [burst packets] [ecn] [harddrop] [bandwidth rate]  [probability chance] [adaptive]

6.5.4. 引數

  • min: 可能進行標記的平均佇列大小。預設為 max/3.
  • max: 當平均佇列大小達到該值後,標記的概率值是最大的。為了避免同步重傳,應該至少是min的兩倍,且大於最小的min值,預設為limit/4。
  • probability: 標記的概率的最大值,為0.0到1.0之間的浮點數,建議值為0.01或0.02(分別表示1%或2%),預設為0.02。
  • limit: 真實(非平均)佇列的硬性限制,單位為位元組。應該大於max+burst的值。建議將這個值設定為最大值的幾倍。
  • burst: 用於決定平均佇列大小受真實佇列大小影響的速度。更大的值會延緩計算的速度,從而允許在標記開始之前出現更長的流量突發。實際經驗遵循如下準則: (min+min+max)/(3*avpkt)。
  • Avpkt: 單位是位元組,與burst一起確定平均佇列長度的時間常數。建議值1000。
  • bandwidth: 該速率用於在一段空閒時間之後計算平均佇列的大小,設定為介面的頻寬。並不意味著RED會進行整流。可選,預設值為10Mbit。
  • ecn: 如上所述,RED可以執行"標記"或"丟棄",顯式擁塞通知允許RED通知遠端主機它們的速率超過了可用頻寬。不支援ECN的主機僅會通知報文丟棄。如果指定了該引數,支援ECN的主機上的報文只會被標記而不會被丟棄(除非佇列達到limit的位元組數),推薦使用。
  • harddrop: 如果平均流大小大於max位元組數,該引數會強制丟棄報文,而不是進行ECN標記。
  • adaptive: (linux-3.3新加的功能 ) 在自適應模式中設定RED,參見 http://icir.org/floyd/papers/adaptiveRed.pdf,自適應的RED的目的是在1%和50%之間動態設定probability,以達到目標平均佇列:(max-min)/2。

6.5.5. 例子

# tc qdisc add dev eth0 parent 1:1 handle 10: red limit 400000 min 30000 max 90000 avpkt 1000 burst 55 ecn adaptive bandwidth 10Mbit

6.6. GRED, Generic Random Early Drop

GRED用在DiffServ 實現中,且在物理佇列中包含虛擬佇列(VQ)。當前,虛擬佇列的數值限制為16。

GRED分兩步配置:首先是通用的引數,用於選擇虛擬佇列DPs的數目,以及是否開啟類RIO的緩衝區共享方案。此時會選擇一個預設的虛擬佇列。

其次為單獨的虛擬佇列設定引數。

6.6.1. 用法

... gred DP drop-probability limit BYTES min BYTES max BYTES avpkt BYTES burst PACKETS probability PROBABILITY bandwidth KBPS [prio value]

OR

... gred setup DPs "num of DPs" default "default DP" [grio]

6.6.2. 引數

  • setup: 表示這是一個GRED的通用設定
  • DPs: 虛擬佇列的數目
  • default: 指定預設的虛擬佇列
  • grio: 啟用類RIO的緩衝方案
  • limit: 定義虛擬佇列的"物理"限制,單位位元組
  • min: 定義最小的閾值,單位位元組
  • max: 定義最大的閾值,單位位元組
  • avpkt: 平均報文大小,單位位元組
  • bandwidth: 介面的線路速度
  • burst: 允許突發的平均大小的報文數目
  • probability: 定義丟棄的概率範圍 (0…);
  • DP: 標識分配給這些引數的虛擬佇列;
  • prio: 如果在通用引數中設定了grio,則表示虛擬佇列的優先順序

6.7. TBF,令牌桶過濾器

該qdisc構建在令牌和桶上。它會對介面上傳輸的流量進行整形(支援整流)。為了限制特定介面上出佇列的報文的速度,TBF qdisc是個不錯的選擇。它僅會將傳輸的流量下降到特定的速率。

只有在包含足夠的令牌時才能傳輸報文。否則,會推遲報文的傳送。以這種方式延遲的報文將報文的往返時間中引入人為的延遲。

6.7.1. 演算法

如其名稱所示,對流量的過濾會基於消耗的令牌。令牌會大致對應到位元組數,每個報文會消耗一個令牌,無論該報文有多小。這樣會導致零位元組的報文佔用一定的鏈路時間。在建立時,TBF會儲存一定的令牌,以應對一次性流量突發的量。令牌會以穩定的速率放到桶中,直到桶滿為止。如果沒有可用的令牌,則報文會保留在佇列中(佇列中的報文不能超過配置的上限)。TBF會計算令牌的虧空,並進行節流,直到可以傳送佇列中的第一個報文。如果不能接收最大速度的報文突發,可以配置峰值速率來限制桶清空的速度。峰值速率使用第二個TBF來實現,其桶相對較小,因此不會發生突發。

6.7.2. 引數

  • limit or latency: limit表示可以在佇列中等待令牌的位元組數。還可以通過設定延遲引數的方式變相地設定此值,延遲引數指定了報文可以在TBF中停留的最大時間。後者的計算會使用到桶的大小,速率和峰值速率(如果設定) 。這兩個引數是互斥的。

  • Burst: 即突發或最大突發。等於桶的大小,單位是位元組。這是令牌在一瞬間可用的最大位元組數。通常大的整流速率需要大的緩衝。如對於Intel上的10mbit/s,則至少需要10kbyte的緩衝才能跟上配置的速率。如果快取過小,可能導致報文丟失,此時每個時間點到達的報文要大於桶的可用容量。最小的緩衝大小可以用頻率除以HZ計算出來。

    對令牌使用的計算是通過一個表來計算的,該表預設情況下有8個報文的解析度。可以通過指定突發的單元大小來更改此解析度。例如,為了指定一個6000位元組的緩衝,其單元大小為16位元組,需要將突發設定為6000/16。單元大小必須是2的整數次冪。

  • Mpu: 零大小的報文不會使用零頻寬。對於乙太網來說,報文的大小不能小於64位元組。最小的報文單元(MTU)決定了一個報文使用的最小令牌(單位位元組),預設是0。

  • Rate: 速度旋鈕。此外,如果需要峰值速率,可以使用以下引數:

  • peakrate: 桶的最大消耗速率。只有在需要毫秒級別的整流時才會用到峰值速率。

  • mtu/minburst: 指定了峰值速率的桶大小。為了精確計算,應該將其設定為MTU的大小。如果用了峰值速率,但有些突發又是可以接受的,則可以增加該值的大小。一個3000位元組的minburst可以允許3mbit/s的峰值速率,支援1000位元組的報文。與常規的突發大小一樣,也可以指定單元大小。

6.7.3. 例子

例10. 建立一個 256kbit/s 的TBF

[root@leander]# cat tbf.tcc
/*
 * make a 256kbit/s TBF on eth0
 *
 */

dev eth0 {
    egress {
        tbf( rate 256 kbps, burst 20 kB, limit 20 kB, mtu 1514 B );
    }
}
[root@leander]# tcc < tbf.tcc
# ================================ Device eth0 ================================

tc qdisc add dev eth0 handle 1:0 root dsmark indices 1 default_index 0
tc qdisc add dev eth0 handle 2:0 parent 1:0 tbf burst 20480 limit 20480 mtu 1514 rate 32000bps

相關文章