Linux TC 帶寬管理隊列規則

      在着手學習TC之前,請先了解TC採用如下單位來描述帶寬:

      mbps = 1024 kbps = 1024 * 1024 bps => byte/s

      mbit = 1024 kbit => kilo bit/s

      mb = 1024 kb = 1024 * 1024 b => byte

      mbit = 1024 kbit => kilo bit

      內定:數值以bps和b方式儲存。但當設置tc輸出速率時,使用如下表示:

      1Mbit = 1024 Kbit = 1024 * 1024 bps => byte/s

      提醒:bit=1位,相當於一個0 或1(開或關);byte=1字節,相當於一個英文字母的大小。

      8bit=1byte,所以往往聽說帶寬是10M是指bit,我們看到的下載速度是byte/s =10M/8byte的來。

 

隊列和隊列規則

      利用隊列,我們可以控制數據發送的方式。但我們只能對發送數據進行控制(或稱爲流量整形)。

      由於網絡的工作機制,我們無法直接控制別人向我們發送什麼數據。然而,Internet主要依靠TCP/IP,它的一些特性很有用。因爲TCP/IP沒辦法知道兩個主機之間的網絡容量,所以它會試圖越來越快地發送數據(所謂的“慢啓動”),在因爲網絡容量不夠而開始丟失數據時,才放慢發送數據的速度。

      這就像你家門口的郵箱一樣,當郵箱幾乎塞滿了郵件時,你會希望不要再發送郵件了。在Internet上可以做到這一點。如果你有一個路由器,並且希望防止某些主機下載速度太快,你需要在路由器的內網卡——也就是鏈接你的網內主機發送數據包的網卡上進行流量控制。

      你還要保證你正在控制的是流量瓶頸環節。如果你有一個100M以太網卡,而你的路由器的鏈路速度是256k,你必須保證你發送的數據量沒有超過路由器的處理能力。否則,就是路由器在控制鏈路和對帶寬進行整形,而不是你所定義的規則在對帶寬進行控制。可以說,我們需要擁有的隊列必須是一系列鏈路中最慢的環節。

 

簡單的無類隊列規則

      如前所述,利用隊列,我們決定了數據被髮送的方式。無類隊列能夠接收數據和重新編排、延遲或丟棄數據包。

      這可以用作對於整個網卡的流量進行整形,而不細分各種情況。在我們進一步學習分類的隊列規則之前,理解這部分是必不可少的!

      最廣泛應用的規則是pfifo_fast隊列規則,因爲它是缺省配置。還有其他隊列規則,後面會有所介紹。每種隊列都有它們各自的優勢和弱點。

1.pfifo_fast

      這個隊列的特點就像它的名字一樣——先進先出(FIFO),也就是說沒有任何數據包被特殊處理。這個隊列有3個所謂的“頻道”(band)。FIFO規則應用於每一個頻道。並且:如果在0頻道有數據包等待發送,1頻道的包就不會被處理,1頻道和2頻道之間的關係也是如此。

      內核遵照數據包的TOS標記,把帶有“最小延遲”標記的包放進0頻道。

      *參數與使用*

      pfifo_fast隊列規則作爲硬性的缺省設置,不能對它進行配置。

      其缺省配置如下:

priomap:

      內核規則,根據數據包的優先權情況,映射到相應的頻道。這個映射過程是根據數據包的TOS字節進行的。

      TOS是這樣的:

      

      TOS字段的4個bit是如下定義的:

      

      因爲在這4bit的後面還有一個bit(MBZ),所以TOS字段的實際值是上述值的2倍。tcpdump -v -v命令可以讓你看到整個TOS字段的情況,而不僅僅是這4個bit,也就是你在下表的第一列看到的值:

      

      第二列寫着與4個TOS位相關的數值,接着第三列對應是它們的意義。比如,15表示一個數據包要求最小成本、最大可靠性、最大吞吐量和最小延遲。

      第四列寫出了Linux內核對於TOS位的理解,並表明了它們對應哪種優先權。最後一列表明缺省的權限圖。在命令行裏,缺省的權限圖應該是:

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

      也就是說,比如優先權4將被映射到1頻道。權限圖允許你列出更高的優先權值(只要小於7),它們不對應TOS映射,但是有其它的意圖。

      下表來自RFC 1349,告訴你應用程序可能如何設置它們的TOS:

      

tx queuelen

      隊列的長度來自網卡的配置,你可以用ifconfig和ip命令修改。

      如設置隊列長度爲10,執行:ifconfig eth0 txqueuelen 10(不能用tc命令設置這個)。

2.令牌桶過濾器(TBF)

      令牌桶過濾器(TBF,Token Bucket Filter)是一個簡單的隊列規則:只允許以不超過事先設定的速率到來的數據包通過,但可能允許短暫突發流量超過設定值。

      TBF很精確,對於網絡和處理器的影響都很小。所以如果您想對一個網卡限速,它應該是最好選擇!

      TBF的實現在於一個緩衝器(桶),該緩衝器(桶)不斷地被一些叫做”令牌”的虛擬數據以特定速率(token rate)填充着。桶最重要的參數就是它的大小,也就是它能夠存儲令牌的數量。

      每個到來的令牌從數據隊列中收集一個數據包,然後從桶中被刪除。這個算法關聯到兩個流上——令牌流和數據流,於是我們得到3種情景:

      *數據流以等於令牌流的速率到達TBF。這種情況下,每個到來的數據包都能對應一個令牌,然後無延遲地通過隊列。

      *數據流以小於令牌流的速度到達TBF。通過隊列的數據包只消耗了一部分令牌,剩下的令牌會在桶裏積累下來,直到桶被裝滿。剩下的令牌可以在需要以高於令牌流速率發送數據流的時候消耗掉,這種情況下會發生突發傳輸。

      *數據流以大於令牌流的速率到達TBF。這意味着桶裏的令牌很快就會被耗盡。導致TBF中斷一段時間,稱爲”越限”(overlimit)。如果數據包持續到來,將發生丟包。

      第三種情景非常重要,因爲它會對數據通過過濾器的速率進行整形。令牌的積累可以導致越限的數據進行短時間的突發傳輸而不必丟包,但是持續越限的話會導致傳輸延遲直至丟包。實際的實現是針對數據的字節數進行的,而不是針對數據包進行的。

      *參數與使用*

      TBF提供了一些可調控的參數。第一個參數永遠可用:

limit/latency

      limit確定最多有多少數據(字節數)在隊列中等待令牌。你也可以通過設置latency來指定這個參數,latency參數確定了一個包在TBF中等待傳輸的最長等待時間。兩者計算決定桶的大小、速率和峯值速率。

burst/buffer/maxburst

      桶的大小,以字節計。這個參數指定了最多可以有多少個令牌能夠即刻被使用。通常,管理的帶寬越大,需要的緩衝器就越大。在Intel體系上,10Mbit/s的速率需要至少10k字節的緩衝區才能達到期望的速率。

如果你的緩衝區太小,就會導致到達的令牌沒有地方放(桶滿了),這會導致潛在的丟包。

MPU

      一個零長度的包並不是不耗費帶寬。比如以太網,數據幀不會小於64字節。MPU(Minimum Packet Unit,最小分組單元)決定了令牌的最低消耗。

rate

      速度操縱桿。參見上面的limit。

      如果桶裏存在令牌而且允許沒有令牌,相當於不限制速率(缺省情況)。如果不希望這樣,可以調入以下參數:

peakrate(峯值速率)

      如果有可用的令牌,數據包一旦到來就會立刻被髮送出去,就像光速一樣。那可能並不是你希望的,特別是你有一個比較大的桶的時候。

      峯值速率可以用來指定令牌以多快的速度被刪除。用書面語言來說,就是:釋放一個數據包,然後等待足夠的時間後再釋放下一個。我們通過計算等待時間來控制峯值速率。例如:UNIX定時器的分辨率是10毫秒,如果平均包長10kb,我們的峯值速率被限制在了1Mbps。

MTU(Maximum Transmission Unit, 最大傳輸單元)/minburst

      但是如果你的常規速率比較高,1Mbps的峯值速率就需要調整。要實現更高的峯值速率,可以在一個時鐘週期內發送多個數據包。最有效的辦法就是:再創建一個令牌桶!這第二個令牌桶缺省情況下爲一個單個的數據包,並非一個真正的桶。

      要計算峯值速率,用MTU乘以100就行了。(應該說是乘以HZ數,Intel體系上是100,Alpha體系上是1024)

      *配置範例*

      這是一個非常簡單而實用的例子:

      # tc qdisc add dev ppp0 root tbf rate 220kbit latency 50ms burst 1540

      爲什麼它很實用呢?如果你有一個隊列較長的網絡設備,比如DSL modem或者cable modem什麼的,並通過一個快速設備(如以太網卡)與之相連,你會發現上傳數據會破壞交互性。

      這是因爲上傳數據會充滿modem的隊列,而這個隊列爲了改善上載數據的吞吐量而設置的特別大。你可能爲了提高交互性只需要一個不太大的隊列,也就是說你希望在發送數據的時候乾點其他的事情。

      上面的命令行並非直接影響了modem中的隊列,而是通過控制Linux中的隊列而放慢了發送數據的速度。

把220kbit修改爲你實際的上載速度再減去幾個百分點。如果你的modem確實很快,就把“burst”值提高一點。

3.隨機公平隊列(SFQ)

      SFQ(Stochastic Fairness Queueing,隨機公平隊列)簡單實現公平隊列算法。它的精確性不如其它的方法,但是它在實現高度公平的同時,需要的計算量卻很少。

      SFQ的關鍵詞是”會話”或“流”,主要針對一個TCP會話或者UDP流。流量被分成相當多數量的FIFO隊列中,每個隊列對應一個會話。數據按照簡單輪轉的方式發送,每個會話都按順序得到發送機會。

      這種方式非常公平,保證了每一個會話都不會沒其它會話所淹沒。SFQ之所以被稱爲“隨機”,是因爲它並不是真的爲每一個會話創建一個隊列,而是使用一個散列算法,把所有的會話映射到有限的幾個隊列中去。因爲使用了散列,所以可能多個會話分配在同一個隊列裏,從而需要共享發包的機會,也就是共享帶寬。爲了不讓這種效應太明顯,SFQ會頻繁地改變散列算法,以便把這種效應控制在幾秒鐘之內。

      有很重要的一點需要聲明:只有當你的輸出網卡確實已經擠滿了的時候,SFQ纔會起作用!否則在你的Linux機器中根本就不會有隊列,SFQ也就不會起作用。稍後我們會描述如何把SFQ與其它的隊列規則結合在一起,以保證兩種情況下都有比較好的結果。

      特別地,在你使用DSL modem或者cable modem的以太網卡上設置SFQ而不進行任何進一步地流量整形是無謀的!

      *參數與使用*

      SFQ基本上不需要手工調整:

      perturb: 多少秒後重新配置一次散列算法。如果取消設置,散列算法將永遠不會重新配置(不建議這樣做)。10秒應該是一個合適的值。

      quantum: 一個流至少要傳輸多少字節後才切換到下一個隊列。缺省設置爲一個最大包的長度(MTU的大小)。不要設置這個數值低於MTU!

      *配置範例*

      如果你有一個網卡,它的鏈路速度與實際可用速率一致——比如一個電話Modem——如下配置可以提高公平性:

      # tc qdisc add dev ppp0 root sfq perturb 10

      # tc -s -d qdisc ls

      qdisc sfq 800c: dev ppp0 quantum 1514b limit 128p flows 128/1024 perturb 10sec

      Sent 4812 bytes 62 pkts (dropped 0, overlimits 0)

      “800c:”這個號碼是系統自動分配的一個句柄號(handle),“limit”意思是這個隊列中可以有128個數據包排隊等待。一共可以有1024個散列目標可以用於速率審計,而其中128個可以同時激活。(no more packets fit in the queue!)每隔10秒種散列算法更換一次。

 

關於如何選用隊列的建議

      總之,我們有幾種簡單的隊列,分別使用排序、限速和丟包等手段來進行流量整形。

      下列提示可以幫你決定使用哪一種隊列。

      *如果想單純地降低出口速率,使用令牌桶過濾器(tbf, token bucket filter)。調節桶的配置可用於控制很高的帶寬。

      *如果你的鏈路已經塞滿了,而你想保證不會有某一個會話獨佔出口帶寬,使用隨機公平隊列(sfq, stochastical fairness queueing)。

      *如果你有一個很大的骨幹帶寬,並且瞭解了相關技術後,可以考慮前向隨機丟包(red, random eraly drop)。

      *如果希望對入口流量進行“整形”(不是轉發流量),可使用入口流量策略(ingress policer),注意,這不是真正的“整形”。

      *如果你正在轉發數據包,在數據流出的網卡上應用TBF。因爲入口網卡起決定性作用的時候,除非你希望讓數據包從多個網卡流出,在這種情況下還是使用入口策略(ingress policer)。

      *如果你並不希望進行流量整形,只是想看看你的網卡是否有比較高的負載而需要使用隊列,使用pfifo隊列(不是pfifo_fast)。它缺乏內部頻道但是可以統計backlog。(好像更適合用於做流量監控)

      *最後,你可以進行所謂的“社交整形”。你不能通過技術手段解決一切問題。用戶的經驗技巧永遠是不友善的。正確而友好的措辭可能幫助你的正確地分配帶寬!

 

術語

      爲了正確地理解更多的複雜配置,有必要先解釋一些概念。由於這個主題的歷史不長和其本身的複雜性,人們經常在說同一件事的時候使用各種詞彙。

      以下來自draft-ietf-diffserv-model-06.txt,Diffserv路由器的建議管理模型。關於這些詞語的嚴格定義請參考這個文檔。

隊列規則(Queuing Discipline,qdisc)

      管理設備輸入(ingress)或輸出(egress)的算法。

根隊列規則(Root Qdisc)

      根隊列規則就是直接依附於設備的隊列規則。

無類的隊列規則(Classless Qdisc)

      一個內部不包含可配置子類的隊列規則。

分類的隊列規則(Classiful Qdisc)

      一個分類的隊列規則內可以包含更多的類。其中每個類又進一步地包含一個隊列規則,這個隊列規則可以是分類的,也可以是無類的。

類(Classes)

      一個分類的隊列規則可以擁有很多類,類內包含隊列規則。

分類器(Classifier)

      每個分類的隊列規則都需要決定什麼樣的包使用什麼類進行發送。分類器就是做這個用的。

過濾器(Filter)

      分類是通過過濾器完成的。一個過濾器包含若干的匹配條件,如果符合匹配條件,就按此過濾器分類。

調度(Scheduling)

      在分類器的幫助下,一個隊列規則可以裁定某些數據包可以排在其他數據包之前發送。這種處理叫做”調度”,比如此前提到的pfifo_fast就是這樣的。

整形(Shaping)

      在一個數據包發送之前進行適當的延遲,以免超過事先規則好的最大速率,這種處理叫做”整形”。整形在egress處進行。習慣上,通過丟包來降速也經常被稱爲整形。

策略(Policing)

      通過延遲或是丟棄數據包來保證流量不超過事先規則的帶寬。在Linux中,策略總是規定丟棄數據包而不是延遲,即不存在ingress隊列。

Work-Conserving

      對於一個work-conserving隊列規則,如果得到一個數據包,它總是立刻對它進行分發。換句話說,只要網卡(egress隊列規則)允許,它就不會延遲數據包的發送。

non-Work-Conserving

      有些隊列——比如令牌桶過濾器——可能需要暫時停止發包以實現限制帶寬。也就是說它們有時候即使有數據包需要處理,也可能拒絕發送。

      現在我們簡單瞭解了一些術語,讓我們看看它們的位置:

      

      1、整個大方框表示內核。

      2、最左面的箭頭表示從網絡上進入計算機的數據包。它們進入Ingress隊列規則,並有可能被某些過濾器丟棄即所謂策略(在進入內核之前丟棄數據有利於節約CPU時間)。

      3、數據包順利通過的話,如果它是發往本地進程的,就會進入IP協議棧處理並提交給該進程。

      4、如果它需要轉發而不是進入本地進程,就會發往egress。本地進程也可以發送數據,交給Egress分類器。

      5、然後經過審查,並放入若干隊列規則中的一個進行排隊。這個過程叫做“入隊”。在不進行任何配置的情況下,只有一個egress隊列規則——pfifo_fast——總是接收數據包。

      6、數據包進入隊列後,就等待內核處理並通過某網卡發送。這個過程叫做“出隊”。

      7、這張圖僅僅表示了機器上只有一塊網卡的情況,圖中的箭頭不能代表所有情況。每塊網卡都有它自己的ingress和egress。


分類的隊列規則

      如果你有多種數據流需要進行區別對待,分類的隊列規則就非常有用了。其中一種叫CBQ(Class Based Queueing,基於類的隊列)經常被提起,以至於大家認爲CBQ就是鑑別隊列是否分類的標準,這是不對的:

      CBQ不過是家族中年紀最大的孩子而已,同時也是最複雜的。它並不能爲你做所有你想做的事情。因此很多人認爲不可思議,因爲他們受“sendmail效應”影響較深,總是認爲複雜的且沒有文檔的技術肯定是最好的。

1.分類的隊列規則及其類中的數據流向

      一旦數據包進入一個分類的隊列規則,它就得被送到某一個類中——也就是需要分類。對數據包進行分類的工具是過濾器。一定要記住:“分類器”是從隊列規則內部調用的,而不是從其他地方。

      過濾器會返回一個決定,隊列規則就根據這個決定把數據包送入相應的類進行排隊。每個子類都可以再次使用它們自己的過濾器進行進一步的分類,直到不需要分類爲止,數據包才進入該類包含的隊列規則等待處理。

      除了能夠包含其它隊列規則之外,絕大多數分類的隊列規則還能夠進行流量整形。這對於需要同時進行調度(如使用SFQ)和流量控制的場合非常有用。

      如果你僅僅使用SFQ,那什麼用也沒有。因爲數據包進、出路由器時沒有任何延遲。雖然你的輸出網卡遠遠快於實際連接速率,但路由器中卻沒有隊列可以調度。

2.隊列規則家族:根、句柄、兄弟和父輩

      每塊網卡都有一個出口“根隊列規則”,缺省情況下是前面提到的pfifo_fast隊列規則。每個隊列規則都指定一個句柄(就是隊列代號),以便以後的配置語句能夠引用這個隊列規則。除了出口隊列規則之外,每塊網卡還有一個入口隊列規則以便對進入的數據流進行策略調整。

      隊列規則的句柄有兩個部分:一個主號碼和一個次號碼。習慣上把根隊列規則稱爲“1:”,等價於“1:0”。隊列規則的次號碼永遠是0。

      類的主號碼必須與它們父輩的主號碼一致。

      *如何用過濾器進行分類*

      下圖給出一個典型的分層關係:

      

      數據包是在根隊列規則處入隊和出隊的,而內核只與“根”打交道。

      一個數據包可能是按照下面這個鏈狀流程進行分類的:

      1: -> 1:1 -> 12: -> 12:2

      數據包現在應該處於12:2下屬的某個隊列規則中的某個隊列中。在這個例子中,樹的每個節點都附帶着一個過濾器,用來選擇下一步進入哪個分支。這樣比較直觀。然而,這樣也是允許的:

      1: -> 12:2

      也就是說,根所附帶的一個過濾器要求把數據包直接交給12:2。

      *數據包如何出隊並交給硬件

      當內核決定把一個數據包發給網卡的時候,根隊列規則1:會得到一個出隊請求,然後把它傳給1:1,然後依次傳給10:、11:和12:,然後試圖從它們中進行dequeue(出隊)操作。也就是說,內核需要遍歷整棵樹,因爲只有12:2中才有這個數據包。換句話說,類及其兄弟僅僅與其“父隊列規則”進行交談,而不會與網卡進行交談。只有根隊列規則才能由內核操作進行出隊操作!

      更進一步,任何類的出隊操作都不會比它們的父類更快。這恰恰是你所需要的:我們可以把SFQ作爲一個子類,放到一個可以進行流量整形的父類中,從而能夠同時得到其父類的流量整形功能和SFQ的調度功能。

3.PRIO隊列規則

      PRIO隊列規則並不進行流量整形,它僅僅根據配置的過濾器把流量進一步細分。你可以認爲PRIO隊列規則是pfifo_fast的一種衍生物,區別在每個頻道都是一個單獨的類,而非簡單的FIFO。

      當數據包進入PRIO隊列規則後,根據你給的過濾器設置選擇一個類,缺省情況下有三個類。這些類僅包含純FIFO隊列規則而沒有更多的內部結構。你可以把它們替換成你需要的任何隊列規則。

       每當有一個數據包需要出隊時,首先處理:1類。只有當標號更小的類中沒有需要處理的數據時,纔會處理標號更大的類。

      當你希望不僅僅依靠包的TOS,而是想使用TC所提供的更強大的功能來進行數據包的優先權劃分時,可以使用這個隊列規則。它也可以包含更多的隊列規則,而pfifo_fast卻只能包含簡單的fifo隊列規則。

      因爲它不進行整形,所以使用時與SFQ有相同的考慮:要麼確保這個網卡的帶寬確實已經佔滿,要麼把它包含在一個能夠整形的分類的隊列規則的內部。嚴格地說,PRIO隊列規則是一種Work-Conserving調度。

      *PRIO的參數與使用*

      PRIO識別下列參數:

bands
       創建頻道的數目。每個頻道實際上就是一個類。如果你修改了這個數值,你必須同時修改:

priomap

      如果你不給TC提供任何過濾器,PRIO隊列規則將參考TC_PRIO的優先級來決定如何給數據包入隊。

      它的行爲就像前面提到過的pfifo_fast隊列規則(先入先出)

      其實頻道是類,缺省情況下命名爲“主標號:1”到“主標號:3”。如果你的PRIO隊列規則是“12: ”,把數據包過濾到“12:1”將得到最高優先級。注意:0頻道的次標號是1;1頻道的次標號是2,以此類推。

      *配置範例*

      我們想創建這樣一個樹:

      

      大批量數據使用30:交互數據使用20:或10:。

      命令如下:

      #tc qdisc add dev eth0 root handle 1: prio

      ##這個命令立即創建了類:1:1, 1:2, 1:3

 

      #tc qdisc add dev eth0 parent 1:1 handle 10: sfq

      #tc qdisc add dev eth0 parent 1:2 handle 20: tbf rate 20kbit buffer 1600 limit 3000

      #tc qdisc add dev eth0 parent 1:3 handle 30: sfq

      我們看看結果如何:

      # tc -s qdisc ls dev eth0

      qdisc sfq 30: quantum 1514b

      Sent 0 bytes 0 pkts (dropped 0, overlimits 0)

 

      qdisc tbf 20: rate 20Kbit burst 1599b lat 667.6ms

      Sent 0 bytes 0 pkts (dropped 0, overlimits 0)

 

      qdisc sfq 10: quantum 1514b

      Sent 132 bytes 2 pkts (dropped 0, overlimits 0)

 

      qdisc prio 1: bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1

      Sent 174 bytes 3 pkts (dropped 0, overlimits 0)

      如你所見,0頻道已經有了一些流量,那是在運行這個命令之後發送了一個包!

      現在我們來點大批量數據傳輸(使用能夠正確設置TOS標記的工具):

      # scp tc [email protected]:./

      [email protected]’s password:

      tc 100% |*****************************| 353 KB  00:00

      # tc -s qdisc ls dev eth0

      qdisc sfq 30: quantum 1514b

      Sent 384228 bytes 274 pkts (dropped 0, overlimits 0)

 

      qdisc tbf 20: rate 20Kbit burst 1599b lat 667.6ms

      Sent 2640 bytes 20 pkts (dropped 0, overlimits 0)

 

      qdisc sfq 10: quantum 1514b

      Sent 2230 bytes 31 pkts (dropped 0, overlimits 0)

 

      qdisc prio 1: bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1

      Sent 389140 bytes 326 pkts (dropped 0, overlimits 0)

      如你所見,所有的流量都是經過30:處理的,優先權最低。現在我們驗證一下交互數據傳輸經過更高優先級的頻道,我們生成一些交互數據傳輸:

      # tc -s qdisc ls dev eth0

      qdisc sfq 30: quantum 1514b

      Sent 384228 bytes 274 pkts (dropped 0, overlimits 0)

 

      qdisc tbf 20: rate 20Kbit burst 1599b lat 667.6ms

      Sent 2640 bytes 20 pkts (dropped 0, overlimits 0)

 

      qdisc sfq 10: quantum 1514b

      Sent 14926 bytes 193 pkts (dropped 0, overlimits 0)

 

      qdisc prio 1: bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1

      Sent 401836 bytes 488 pkts (dropped 0, overlimits 0)

      工作正常,所有額外的流量都是經10:這個更高優先級的隊列規則處理的。與先前的整個scp不同,沒有數據經過最低優先級的隊列規則。

4.著名的CBQ隊列規則

      如前所述,CBQ是最複雜、最瑣碎、最難以理解、最刁鑽的隊列規則。這並不是因爲其作者的惡毒或者不稱職,而是因爲CBQ算法本身的不精確,而且與Linux的內在機制不協調造成的。

      除了可以分類之外,CBQ也是一個整形器,但是從表面上看來工作得並不好。它應該是這樣的:如果你試圖把一個10Mbps的連接整形成1Mbps的速率,就應該讓鏈路90%的時間處於閒置狀態,必要的話我們就強制,以保證90%的閒置時間。

      但閒置時間的測量非常困難,所以CBQ就採用了它一個近似值——來自硬件層的兩個傳輸請求之間的毫秒數來代替它。這個參數可以近似地表現這個鏈路的繁忙程度。

      這樣做相當慎重,而且不一定能夠得到正確的結論。比如,由於驅動程序方面或者其它原因造成一塊網卡的實際傳輸速率不能夠達到它的標稱速率,該怎麼辦?由於總線設計的原因,PCMCIA網卡永遠也不會達到100Mbps。那麼我們該怎麼計算閒置時間呢?

      如果我們引入非物理網卡——像PPPoE、PPTP——情況會變得更糟糕。因爲相當一部分有效帶寬耗費在了鏈路維護上。那些做過了測試的人們都發現CBQ總是不精確甚至完全失去了其本來意義。但是,在很多場合下它還是能夠很好地工作。根據下面的文檔,你應該能夠較好地配置CBQ來解決大多數問題。

      *CBQ整形的細節:*

      CBQ的工作機制是確認鏈路的閒置時間足夠長,以達到降低鏈路實際帶寬的目的。爲此,它要計算兩個數據包的平均發送間隔。

      操作期間,有效閒置時間的測量使用EWMA(exponential weighted moving average,指數加權移動均值)算法,也就是說最近處理的數據包的權值比以前的數據包按指數增加。UNIX的平均負載也是這樣算出來的。

      計算出來的平均時間值減去EWMA測量值,得出的結果叫做“avgidle”。最佳的鏈路負載情況下,這個值應當是0:數據包嚴格按照計算出來的時間間隔到來。

      在一個過載的鏈路上,avgidle值應當是負的。如果這個負值太嚴重,CBQ就會暫時禁止發包,稱爲”overlimit”(越限)。

      相反地,一個閒置的鏈路應該有很大的avgidle值,這樣閒置幾個小時後,會造成鏈路允許非常大的帶寬通過。爲了避免這種局面,我們用maxidle來限制avgidle的值不能太大。

      理論上講,如果發生越限,CBQ就會禁止發包一段時間(長度就是事先計算出來的傳輸數據包之間的時間間隔),然後通過一個數據包後再次禁止發包。但是最好參照下面的minburst參數。

      下面是配置流量整形時需要指定的一些參數:

avpkt

      平均包大小,單位是字節。計算maxidle(最大閒置)時需要,maxidle從maxburst得出。

bandwidth

      網卡的物理帶寬,用來計算閒置時間。

cell

      一個數據包被髮送出去的時間可以是基於包長度而階梯增長的。一個800字節的包和一個806字節的包可以認爲耗費相同的時間。也就是說它用作設置時間力度。通常設置爲8,必須是2的整數次冪。

maxburst

      這個參數的值決定了計算maxidle所使用的數據包的個數。在avgidle跌落到0之前,這麼多的數據包可以突發傳輸出去。這個值越高,越能夠容納突發傳輸。你無法直接設置maxidle的值,必須通過這個參數來控制。

minburst

      如前所述,發生越限時CBQ會禁止發包。實現這個的理想方案是根據事先計算出的閒置時間進行延遲之後,發一個數據包。然而,UNIX的內核一般來說都有一個固定的調度週期(一般不大於10ms),所以最好是這樣:禁止發包的時間稍長一些,然後突發性地傳輸minburst個數據包,而不是一個一個地傳輸。等待的時間叫做offtime。

      從大的時間尺度上說,minburst值越大,整形越精確。但是,從毫秒級的時間尺度上說,就會有越多的突發傳輸。

minidle

      如果avgidle值降到0,也就是發生了越限,就需要等待,直到avgidle的值足夠大才發送數據包。爲避免因關閉鏈路太久而引起的以外突發傳輸,在avgidle的值太低的時候會被強制設置爲minidle的值。

      參數minidle的值是以負微秒記的。所以10代表avgidle被限制在-10us上。

mpu

      最小分組大小——因爲即使是0長度的數據包,在以太網上也要生成封裝成64字節的幀,而需要一定時間去傳輸。爲了精確計算閒置時間,CBQ需要知道這個值。

rate

      期望中的傳輸速率。也就是“速度操縱桿”!

      在CBQ的內部有很多的微調參數。比如,那些已知隊列中沒有數據的類就不參加計算、越限的類將被懲罰性地降低優先級等等。都非常巧妙和複雜。

      *CBQ在分類方面的行爲*

      除了使用上述idletime近似值進行整形之外,CBQ還可以象PRIO隊列那樣,把各種類賦予不同的優先級,優先權數值小的類會比優先權值大的類被優先處理。

      每當網卡請求把數據包發送到網絡上時,都會開始一個WRR(weighted round robin,加權輪轉)過程,從優先權值小的類開始。

      那些隊列中有數據的類就會被分組並被請求出隊。在一個類收到允許若干字節數據出隊的請求之後,再處理下一個相同優先權值的類。

      *下面是控制WRR過程的一些參數:*

allot

      當從外部請求一個CBQ發包的時候,它就會按照“priority(prio)”參數指定的順序輪流嘗試其內部的每一個類的隊列規則。當輪到一個類發數據時,它只能發送一定量的數據。“allot”參數就是這個量的基值。更多細節請參照“weight”參數。

prio

      CBQ可以象PRIO設備那樣工作。其中“prio”值較低的類只要有數據就必須先服務,其他類要延後處理。

weight

      “weight”參數控制WRR過程。每個類都輪流取得發包的機會。如果其中一個類要求的帶寬顯著地高於其他的類,就應該讓它每次比其他的類發送更多的數據(以字節爲單位,可以理解爲偏袒數量,例如weight 200Kbit就相當於每次處理優先級的數據比普通數據多處理200Kbit)。

      CBQ會把一個類下面所有的weight值加起來後歸一化,所以數值可以任意定,只要保持比例合適就可以。人們常把“速率/10”作爲參數的值來使用,實際工作得很好。歸一化值後的值乘以“allot”參數後,決定了每次傳輸多少數據。

      *決定鏈路的共享和借用的CBQ參數*

      除了純粹地對某種數據流進行限速之外,CBQ還可以指定哪些類可以向其它哪些類借用或者出借一部分帶寬。

Isolated/Sharing (isolated字面意思:獨立,單獨的)

      凡是使用“isolated”選項配置的類,就不會向其兄弟類借出帶寬。如果你的鏈路上同時存在着不友好的人,你就可以使用這個選項。

      選項“sharing”是“isolated”的反義選項。

Bounded/Borrow (bounded字面意思:受限制的,有限的;borrow=借入)

      一個類也可以用“bounded”選項配置,意味着它不會向其兄弟類借入帶寬。選項“borrow”是“bounded”的反義選項。

      一個典型的情況就是你的一個鏈路上有多個客戶都設置成了“isolated”和“bounded”,那就是說他們都被限制在其要求的速率之下,且互相之間不會借用帶寬(就是我們常說的帶寬獨享)。在這樣的一個類的內部的子類之間是可以互相借用帶寬的。

      *配置範例*

            

      這個配置把WEB服務器的流量控制爲5mbit、SMTP流量控制在3mbit上。而且二者一共不得超過6mbit,互相之間允許借用帶寬。我們的網卡是100Mbps的。

# tc qdisc add dev eth0 root handle 1:0 cbq bandwidth 100Mbit avpkt 1000 cell 8

# tc class add dev eth0 parent 1:0 classid 1:1 cbq bandwidth 100Mbit rate 6Mbit weight 0.6Mbit prio 8 allot 1514 cell 8 maxburst 20 avpkt 1000 bounded

      這部分按慣例設置了根爲1:0,並且綁定了類1:1。也就是說整個帶寬不能超過6Mbps。

      如前所述,CBQ需要調整很多的參數。其實所有的參數上面都解釋過了。但是,相應的的HTB配置則要簡明得多。

# tc class add dev eth0 parent 1:1 classid 1:3 cbq bandwidth 100Mbit rate 5Mbit weight 0.5Mbit prio 5 allot 1514 cell 8 maxburst 20 avpkt 1000

# tc class add dev eth0 parent 1:1 classid 1:4 cbq bandwidth 100Mbit rate 3Mbit weight 0.3Mbit prio 5 allot 1514 cell 8 maxburst 20 avpkt 1000

      我們建立了2個類。注意我們如何根據帶寬來調整weight參數的。兩個類都沒有配置成“bounded”,但它們都連接到了類1:1上,而1:1設置了 “bounded”。所以兩個類的總帶寬不會超過6Mbps。別忘了,同一個CBQ下面的子類的主號碼都必須與CBQ自己的號碼相一致!

      # tc qdisc add dev eth0 parent 1:3 handle 30: sfq

      # tc qdisc add dev eth0 parent 1:4 handle 40: sfq

      缺省情況下,兩個類都有一個FIFO隊列規則。但是我們把它換成SFQ隊列,以保證每個數據流都公平對待。

      # tc filter add dev eth0 parent 1:0 protocol ip prio 1 u32 match ip sport 80 0xffff flowid 1:3

      # tc filter add dev eth0 parent 1:0 protocol ip prio 1 u32 match ip sport 25 0xffff flowid 1:4

      這些命令規則了根上的過濾器,保證數據流被送到正確的隊列規則中去。

      注意:我們先使用了“tc class add” 在一個隊列規則中創建了類,然後使用“tc qdisc add”在類中創建隊列規則。

      你可能想知道,那些沒有被那兩條規則分類的數據流怎樣處理了呢?從這個例子來說,它們被1:0直接處理,沒有限制。

      如果SMTP+web的總帶寬需求大於6Mbps,那麼這6M帶寬將按照兩個類的weight參數的比例情況進行分割:WEB服務器得到5/8的帶寬,SMTP得到3/8的帶寬。從這個例子來說,可以這麼認爲:WEB數據流總是會得到5/8*6Mbps=3.75Mbps的帶寬。

      *其它CBQ參數:split和defmap (split:分離、分裂)*

      如前所述,一個分類的隊列規則需要調用過濾器來決定一個數據包應該發往哪個類去排隊。

      除了調用過濾器,CBQ還提供了其他方式,defmap和split。很難掌握,但好在無關大局。但是現在是解釋defmap和split的最佳時機,我會盡力解釋。

      因爲你經常是僅僅需要根據TOS來進行分類,所以提供了一種特殊的語法。當CBQ需要決定了數據包要在哪裏入隊時,要檢查這個節點是否爲“split節點”。如果是,子隊列規則中的一個應該指出它接收所有帶有某種優先權值的數據包,權值可以來自TOS字段或者應用程序設置的套接字選項。

      數據包的優先權位與defmap字段的值進行”或”運算來決定是否存在這樣的匹配。換句話說,這是一個可以快捷創建僅僅匹配某種優先權值數據包的過濾器的方法。如果defmap等於0xff,就會匹配所有包,0則是不匹配。下面的實例可以幫助理解:

# tc qdisc add dev eth1 root handle 1: cbq bandwidth 10Mbit allot 1514 cell 8 avpkt 1000 mpu 64

# tc class add dev eth1 parent 1:0 classid 1:1 cbq bandwidth 10Mbit rate 10Mbit allot 1514 cell 8 weight 1Mbit prio 8 maxburst 20 avpkt 1000

      一個標準的CBQ前導。

      Defmap參照TC_PRIO位:

      

      TC_PRIO..的數值對應它右面的bit。關於TOS位如何換算成優先權值的細節可以參照前面pfifo_fast章節。

      然後是交互和大吞吐量的類:

# tc class add dev eth1 parent 1:1 classid 1:2 cbq bandwidth 10Mbit rate 1Mbit allot 1514 cell 8 weight 100Kbit prio 3 maxburst 20 avpkt 1000 split 1:0 defmap c0

# tc class add dev eth1 parent 1:1 classid 1:3 cbq bandwidth 10Mbit rate 8Mbit allot 1514 cell 8 weight 800Kbit prio 7 maxburst 20 avpkt 1000 split 1:0 defmap 3f

      “split隊列規則”是1:0,也就是做出選擇的地方。c0是二進制的11000000,3F是00111111,所以它們共同匹配所有的數據包。第一個類匹配第7和第6位,也就是負責“交互”和“控制”的數據包。第二個類匹配其餘的數據包。

      節點1:0現在應該有了這樣一個表格:

      

      爲了更有趣,你還可以傳遞一個“change掩碼”,確切地指出你想改變哪個優先權值。你只有在使用了“tc class change”的時候才需要。比如,往1:2中添加best effort數據流,應該執行:

      # tc class change dev eth1 classid 1:2 cbq defmap 01/01

      現在,1:0上的優先權分佈應該是:

      

5.HTB(Hierarchical Token Bucket, 分層的令牌桶)

      Martin Devera意識到CBQ太複雜,而且並沒有按照多數常見情況進行優化。他的Hierarchical能夠很好地滿足這樣一種情況:你有一個固定速率的鏈路,希望分割給多種不同的用途使用。爲每種用途做出帶寬承諾並實現定量的帶寬借用。

      HTB就象CBQ一樣工作,但是並不靠計算閒置時間來整形。它是一個分類的令牌桶過濾器。它只有很少的參數,並且在它的網站能夠找到很好的文檔。

      隨着你的HTB配置越來越複雜,你的配置工作也會變得複雜。但是使用CBQ的話,即使在很簡單的情況下配置也會非常複雜!HTB3 (關於它的版本情況,請參閱它的網站)已經成了官方內核的一部分(2.4.20-pre1、2.5.31及其後)。然而,你可能仍然要爲你的TC命令打上HTB3支持補丁,否則你的TC命令不理解HTB3。

      如果你已經有了一個新版內核或者已經打了補丁,請儘量考慮使用HTB。

      *配置範例*

      環境與要求與上述CBQ的例子一樣:

      把WEB服務器的流量控制爲5mbit、SMTP流量控制在3mbit上。而且二者一共不得超過6mbit,互相之間允許借用帶寬。我們的網卡是100Mbps的。

      # tc qdisc add dev eth0 root handle 1: htb default 30

      # tc class add dev eth0 parent 1: classid 1:1 htb rate 6mbit burst 15k

      # tc class add dev eth0 parent 1:1 classid 1:10 htb rate 5mbit burst 15k

      # tc class add dev eth0 parent 1:1 classid 1:20 htb rate 3mbit ceil 6mbit burst 15k

      # tc class add dev eth0 parent 1:1 classid 1:30 htb rate 1kbit ceil 6mbit burst 15k

      作者建議在那些類的下方放置SFQ:

      # tc qdisc add dev eth0 parent 1:10 handle 10: sfq perturb 10

      # tc qdisc add dev eth0 parent 1:20 handle 20: sfq perturb 10

      # tc qdisc add dev eth0 parent 1:30 handle 30: sfq perturb 10

      添加過濾器,直接把流量導向相應的類:

      # U32="tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32"

      # $U32 match ip dport 80 0xffff flowid 1:10

      # $U32 match ip sport 25 0xffff flowid 1:20

      不錯,沒有奇怪的數字,沒有複雜的參數。

      HTB完成得相當不錯,如果10:和20:都得到了保證的速率,剩下的就是分割了

      它們借用的比率是5:3,正如你期望的那樣。

      未被分類的流量被送到了30:,僅有一點點帶寬,但是卻可以任意借用剩下的帶寬。因爲我們內部使用了SFQ,而可以公平發包。


使用過濾器對數據包進行分類

      爲了決定用哪個類處理數據包,必須調用所謂的“分類器鏈” 進行選擇。這個鏈中包含了這個分類隊列規則所需的所有過濾器。

      重複前面那棵樹:

      

      當一個數據包入隊的時候,每一個分支處都會諮詢過濾器鏈如何進行下一步。典型的配置是在1:1處有一個過濾器把數據包交給12:,然後12:處的過濾器在把包交給12:2。

      你可以把後一個過濾器同時放在1:1處,但是你可以通過在鏈路上進行更多特定的測試來使效率得以提高。

      另外,你不能用過濾器把數據包向“上”送。而且,使用HTB的時候應該把所有的規則放到根上!

      再次強調:數據包只能向“下”進行入隊操作!只有出隊的時候纔會上到網卡所在的位置來。它們不會落到樹的最底層後送到網卡!

      *過濾器的一些簡單範例*

      下面,我們開始簡單地匹配一些比較有明顯特徵的語法。

      比方說,我們有一個PRIO隊列規則,叫做“10:”,包含3個類,我們希望把去往22口的數據流發送到最優先的頻道中去。應該這樣設置過濾器:

      # tc filter add dev eth0 protocol ip parent 10: prio 1 u32 match ip dport 22 0xffff flowid 10:1

      # tc filter add dev eth0 protocol ip parent 10: prio 1 u32 match ip sport 80 0xffff flowid 10:1

      # tc filter add dev eth0 protocol ip parent 10: prio 2 flowid 10:2

      意思是說:

      向eth0上的10:節點添加一個u32過濾規則,它的優先權是1:凡是去往22口(精確匹配)的IP數據包,發送到頻道10:1。

      向eth0上的10:節點添加一個u32過濾規則,它的優先權是1:凡是來自80口(精確匹配)的IP數據包,發送到頻道10:1。

      向eth0上的10:節點添加一個過濾規則,它的優先權是2:凡是上面未匹配的IP數據包,發送到頻道10:2。

別忘了添加“dev eth0”(你的網卡或許叫別的名字),因爲每個網卡的句柄都有完全相同的命名空間。

      想通過IP地址進行篩選的話,可以使用以下命令:

      # tc filter add dev eth0 parent 10:0 protocol ip prio 1 u32 match ip dst 4.3.2.1/32 flowid 10:1

      # tc filter add dev eth0 parent 10:0 protocol ip prio 1 u32 match ip src 1.2.3.4/32 flowid 10:1

      # tc filter add dev eth0 protocol ip parent 10: prio 2 flowid 10:2

      這個例子把去往4.3.2.1和來自1.2.3.4的數據包送到了最高優先的隊列,其它的則送到次高權限的隊列。

      你可以連續使用match,想匹配來自1.2.3.4的80口的數據包的話,可以使用命令:

# tc filter add dev eth0 parent 10:0 protocol ip prio 1 u32 match ip src 4.3.2.1/32 match ip sport 80 0xffff flowid 10:1

      *常用到的過濾命令一覽*

      這裏列出的絕大多數命令都根據這個命令改編而來:

      # tc filter add dev eth0 parent 1:0 protocol ip prio 1 u32

      這些是所謂的“u32”匹配,可以匹配數據包的任意部分。

根據源/目的地址

      源地址段 match ip src 1.2.3.0/24

      目的地址段 match ip dst 4.3.2.0/24

      單個IP地址使用“/32”作爲掩碼即可。

根據源/目的端口,所有IP協議

      源端口 match ip sport 80 0xffff

      目的端口 match ip dport 80 0xffff

根據IP協議(tcp, udp, icmp, gre, ipsec)

      使用/etc/protocols所指定的數字。

      比如:icmp是 match ip protocol 1 0xff

根據fwmark(防火牆標記功能)

      你可以使用ipchains/iptables給數據包做上標記,並且這個標記會在穿過網卡的路由過程中保留下來。如果你希望對來自eth0並從eth1發出的數據包做整形,這就很有用了。語法是這樣的:

      #tc filter add dev eth1 protocol ip parent 1:0 prio 1 handle 6 fw flowid 1:1

      注意,這不是一個u32匹配!

      你可以象這樣給數據包打標記:

      # iptables -A PREROUTING -t mangle -i eth0 -j MARK --set-mark 6(數字6是可以任意指定的)

      如果你不想去學習所有的tc語法,就可以與iptables結合,僅僅學習按fwmark匹配就行了。

按TOS字段選擇交互和最小延遲的數據流:

      # tc filter add dev ppp0 parent 1:0 protocol ip prio 10 u32 match ip tos 0x10 0xff flowid 1:4

      想匹配大量傳輸的話,使用“0x08 0xff”。


IMQ(Intermediate queueing device,中介隊列設備)

      中介隊列設備不是一個隊列規則,但它的使用與隊列規則是緊密相連的。

       就Linux而言,隊列規則是附帶在網卡上的,所有在這個網卡上排隊的數據都排進這個隊列規則。所以出現了兩個侷限:

      1.只能進行出口整形(雖然也存在入口隊列規則,但在上面實現分類的隊列規則的可能性非常小)。

      2.一個隊列規則只能處理一塊網卡的流量,無法設置全局的限速。

      IMQ就是用來解決上述兩個侷限的。簡單地說,你可以往一個隊列規則中放任何東西。被打了特定標記的數據包在netfilter的 NF_IP_PRE_ROUTING和NF_IP_POST_ROUTING兩個鉤子函數處被攔截,並被送到一個隊列規則中,該隊列規則附加到一個IMQ設備上。對數據包打標記要用到iptables的一種處理方法。

      這樣你就可以對剛剛進入網卡的數據包打上標記進行入口整形,或者把網卡們當成一個個的類來看待而進行全局整形設置。你還可以做很多事情,比如:把http流量放到一個隊列規則中去、把新的連接請求放到一個隊列規則中去。

      *配置範例*

      我們首先想到的是進行入口整形,以便讓你自己得到高保證的帶寬。就像配置其它網卡一樣:

      #tc qdisc add dev imq0 root handle 1: htb default 20

      #tc class add dev imq0 parent 1: classid 1:1 htb rate 2mbit burst 15k

      #tc class add dev imq0 parent 1:1 classid 1:10 htb rate 1mbit

      #tc class add dev imq0 parent 1:1 classid 1:20 htb rate 1mbit

      #tc qdisc add dev imq0 parent 1:10 handle 10: pfifo

      #tc qdisc add dev imq0 parent 1:20 handle 20: sfq

      #tc filter add dev imq0 parent 10:0 protocol ip prio 1 u32 match ip dst 10.0.0.230/32 flowid 1:10

      在這個例子中,使用了u32進行分類。其它的分類器應該也能實現。然後,被打上標記的包被送到imq0排隊。

      #iptables -t mangle -A PREROUTING -i eth0 -j IMQ --todev 0

      #ip link set imq0 up

      iptables的IMQ處理方法只能用在PREROUTING和POSTROUTING鏈的mangle表中。語法是:

      IMQ [ --todev n ]

      n: imq設備的編號

      注:ip6tables也提供了這種處理方法。

      請注意,如果數據流是事後才匹配到IMQ處理方法上的,數據就不會入隊。數據流進入imq的確切位置取決於這個數據流究竟是流進的還是流出的。下面是netfilter(也就是iptables)在內核中預先定義優先級:

enum nf_ip_hook_priorities {
    NF_IP_PRI_FIRST = INT_MIN,
    NF_IP_PRI_CONNTRACK = -200,
    NF_IP_PRI_MANGLE = -150,
    NF_IP_PRI_NAT_DST = -100,
    NF_IP_PRI_FILTER = 0,
    NF_IP_PRI_NAT_SRC = 100,
    NF_IP_PRI_LAST = INT_MAX,
};

      對於流入的包,imq把自己註冊爲優先權等於NF_IP_PRI_MANGLE+1。也就是說數據包在經過了PREROUTING鏈的mangle表之後才進入imq設備。

      對於流出的包,imq使用優先權等於NF_IP_PRI_LAST,也就是說不會白白處理本應該被filter表丟棄的數據包。

原文鏈接:

http://m.blog.csdn.net/blog/zhaobryant/38797739


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章