Next Previous Contents

8. 用 CBQ 進行頻寬管理 (Using Class Based Queueing for bandwidth management)

現階段由我來發掘這功能﹐委實是折殺小弟了。Linux 2.2 本身就自帶有許多頻寬管理辦法﹐其實已可以與那些高級專屬的頻寬管理系統媲美了。

Linux 所提供的﹐甚至比 Frame 及 ATM 還要更多。

流量控管的兩個基本單元是﹕過濾器(filters) 和佇列(queues)。前者將流量安排至後者﹐後者則收集流量然後決定哪些要先送、哪些要晚送、或是丟棄掉(drop)。兩者都各有其不同系列。

最常見的過濾器是 fwmark 和 u32﹐前者讓您可以使用 Linux netfilter 程式選擇流量﹔而後者則允許您依據任何標頭(header)選擇流量。至於最常聽到的佇列演算法就是 Class Based Queue 了。CBQ 可以說是一個 super-queue﹐在其裡面包含了其它佇列 (甚至其它 CBQs)。

或許﹐關於佇列演算法(queueing)在頻寬管理上的運用﹐一時間尚難一窺全豹﹐不過﹐它還真的不負眾望就是了。

作為我們的參考資料框架﹐我把目前這個章節套用在一個 ISP 模式之上﹐也就是我偷師學藝之所﹐暫且稱之為 Casema Internet in The Netherlands 吧。Casema﹐事實上是一家 cable 公司﹐他們的客戶和他們自己的辦公室都有 internet 的需求。大多數公司電腦都有 internet 的連線。實際上﹐他們有大把金錢可以花﹐而不是用 Linux 來做頻寬管理的。

那讓我們看看我們的 ISP 是怎樣用 Linux 來管理其頻寬吧。

8.1 佇列演算法是什麼東東﹖(What is queueing?)

我們用佇列演算法來判定數據 被傳送 的順序。有一個很重要的概念我們要知道﹐就是我們只能處理那些我們要傳送的數據。那它又是如何影響傳送速度之判定順序呢﹖您可以想像為一位收款員每一分鐘可以處理 3 個客人。

然後要付款的客人就要跑到隊列的 '尾巴' 去排隊。這是所謂的 'FIFO quequeing' (First IN, First Out --- 先入先出)。不過﹐假設我們讓某些客人插入隊列中間﹐而不是排在最後﹐然後這些客人就可以花更少時間在隊列中﹐因而也可以更快的購物。

在 inetnet 的環境中﹐我們沒辦法直接控制別人要送什麼東西過來。這有點像您家中的(實體)信箱﹐您沒辦法影響全世界去修改他們要送信給您的數量﹐除非您有能力通知所有人。

然而﹐internet 賴以為繼的 TCP/IP﹐卻有某些功能可以協助我們。TCP/IP 本身是沒辦法知道兩台主機之間的網路容量的﹐所以它會越來越快(剛開始很慢)的將數據送出去﹐而當封包開始丟失的時候﹐因為沒有空間將他們送出去了﹐然後就會開始減慢下來。

這情形﹐就有點像您還沒讀完一半信件的時候﹐會希望別人不要再寄信給您一樣。不同之處﹐這裡是在 internet 上面而已。

FIXME﹕解釋擁塞視窗 (congestion windows)

[The Internet] ---<E3, T3, whatever>--- [Linux router] --- [Office+ISP]
                                      eth1          eth0

現在﹐我的 Linux router 有兩張界面﹐eth0 和 eth1。eth1 用來連接我們的 router﹐它負責將封包送至光纖線路以及將封包接收進來。

eth0 則用來連接另外一個 subnet﹐內有公司防火牆及我們的網路前端﹐我們透過它來連接客戶。

由於我們僅能限制送出的部份﹐我們需要兩套獨立、但非常近似的規則。透過修改 eth0 的佇列﹐我們可以判定資訊要多快送至我們客戶那邊﹐因而要分配多少下傳(downstream) 頻寬給他們﹐所謂的‘下載速度’是也。

在 eth1 上面﹐我們判定數據要多快送給 Internet、多快給我們的使用者﹐而且公司內部和業務方面均需要上傳數據。

8.2 先搞定頻寬切割 (First attempt at bandwidth division)

CBQ 可以讓我們產生好些不同類別(classes)﹐甚至類別中的類別。至於更大的切割﹐或許可以稱為 '代理(agencies)'。而在這些類別裡面﹐或者可以找到諸如 'bulk' 或 'interactive' 之類的名稱。

例如﹐我們或有一條 10 megabit 的 'internet' 連線﹐以分享給我們的客戶及公司之需。但我們不能讓辦公室裡的少數同仁盜取大量的、本來要賣給客戶的頻寬。

反過來說﹐比方說我們的客戶﹐也不能佔用本來給我們門市通往客戶資料庫的頻寬。

在過去﹐用來解決的辦法﹐莫過於使用 Frame Relay/ATM 或建立虛擬電路(Virtual Circuits)。這的確能解決問題﹐只不過 frame 並不容易十分細緻調整﹐而 ATM 在攜帶 IP 流量上的效能也非常差強人意﹐而且﹐兩者都未有標準法則﹐以產生不同類型的流量進入不同的 VCs 。

不過﹐假如您真的使用 ATM 的話﹐Linux 同樣能架輕就熟的為您表演高難度的流量分類技巧。而另一方法是牽兩條獨立的線路﹐不過﹐似乎不怎麼實在﹐也不十分完善﹐且也不見得能完全解決您的所有問題。

這時候﹐您就要求助於 CBQ 這尊菩薩了。

顯而易見﹐我們這裡主要有兩個類別﹕ 'ISP' 和 'Office' 。剛開始的時候﹐我們真的不必十分講究他們的頻寬切割﹐所以我們就不再於其類別下細分了。

我們決定客戶的下傳流量必須保證在 8 megabits 的範圍﹐而我們的辦公室只有 2 megabits。

可以用 iproute2 之 tc 工具來設定起流量控管。

# tc qdisc add dev eth0 root handle 10: cbq bandwidth 10Mbit avpkt 1000

好了﹐這裡有一堆數字。究竟如何呢﹖我們已經設定起 eth0 的 '佇列戒律 (queueing discipline)' 了。我們以 'root' 來宣告這是頂層(root) discipline。我們還將其 handle 設為 '10'。因為我們這裡要做 CBQ﹐所以我們也同時在命令行中指明。我們告訴核心﹐它可以支配 10M bit ﹐同時平均封包體積大約為 1000 個 octet。

好﹐現在我們就產生我們的頂層類別﹐它凌架於其它的所有類別之上。

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

哇﹐這裡的數字更多﹗Linux 的 CBQ 在實作上其實蠻具通用性的。我們用 'parent 10:0' 來指定此類別是源自我們剛纔產生的 qdisc handel '10:' 這個頂層類別而來的。而 'classid 10:1' 呢﹐則是我們授予這個類別的名稱。

我們這裡無須告訴核心更多資訊﹐單純產生一個類別以滿足可用設備就是了。我們同時還指定出 MTU(外加一些 overhead) 為 1514 個 octet。而且﹐我們量定此類別的 '比重(weight) ' 為 1Mbit﹐此為一個微調參數而已。

現在﹐再讓我們產生 ISP 類別吧﹕

# tc class add dev eth0 parent 10:1 classid 10:100 cbq bandwidth 10Mbit rate \
  8Mbit allot 1514 weight 800Kbit prio 5 maxburst 20 avpkt 1000 \
  bounded

我們先撥出 8Mbit﹐同時用 'bounded' 參數來指定此類別一定不能超過此限制。否則此類別或會從其它類別那裡借用頻寬﹐我們後面將會討論到。

還是讓我們產生Office 之頂層類別﹐趕快鳴金收兵吧﹕

# tc class add dev eth0 parent 10:1 classid 10:200 cbq bandwidth 10Mbit rate \
  2Mbit allot 1514 weight 200Kbit prio 5 maxburst 20 avpkt 1000 \
  bounded

為了幫助我們更好的理解﹐下面的圖示列出了所有的類別﹕

+-------------[10: 10Mbit]-------------------------+
|+-------------[10:1 root 10Mbit]-----------------+|
||                                                ||
|| +-----[10:100 8Mbit]---------+ [10:200 2Mbit]  ||
|| |                            | |            |  ||
|| |          ISP               | |   Office   |  ||
|| |                            | |            |  ||
|| +----------------------------+ +------------+  ||
||                                                ||
|+------------------------------------------------+|
+--------------------------------------------------+

好了﹐現在我們已經告訴核心有什麼類別存在﹐但還沒說要怎樣去管理佇列。我們不如速戰速決﹐一次搞定兩個類別吧。

# tc qdisc add dev eth0 parent 10:100 sfq quantum 1514b perturb 15
# tc qdisc add dev eth0 parent 10:200 sfq quantum 1514b perturb 15

目前的範例中﹐我們安裝的是 Stochastic Fairness Queueing discipline (sfq)﹐雖然不是很貼切﹐不過它卻可以在不耗費更多 CPU 運轉之下﹐很好的處理大量頻寬。而我們通常使用的會是 The Token Bucket Filter。

到目前為止﹐我們僅剩一件事情要做而已﹐就是向核心解釋什麼樣的封包屬於什麼樣的類別。剛開始﹐我們純粹用 iproute2 來做就好﹐但是﹐配合 netfilter 雙劍合璧的話﹐更是如虎添翼。

# tc filter add dev eth0 parent 10:0 protocol ip prio 100 u32 match ip dst \
  150.151.23.24 flowid 10:200

# tc filter add dev eth0 parent 10:0 protocol ip prio 25 u32 match ip dst \
  150.151.0.0/16 flowid 10:100

這裡﹐我們假設辦公室網路躲在位址為 150.151.23.24 的防火牆之後﹐而我們其它 IP 位址則屬於 ISP 的。

使用 u32 比對(match) 更是容易﹐而用 netfilter 來標識封包也更能夠設定出精密的比對規則﹐然後我們可以在 tc 裡進行比對。

好了﹐現在我們已經將下傳頻寬切割完畢﹐同樣的﹐我們在上傳頻寬上面依樣畫葫蘆。為求簡捷﹐這次讓我們一鼓作氣﹐直搗黃龍﹕

# tc qdisc add dev eth1 root handle 20: cbq bandwidth 10Mbit avpkt 1000

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

# tc class add dev eth1 parent 20:1 classid 20:100 cbq bandwidth 10Mbit rate \
  8Mbit allot 1514 weight 800Kbit prio 5 maxburst 20 avpkt 1000 \
  bounded

# tc class add dev eth1 parent 20:1 classid 20:200 cbq bandwidth 10Mbit rate \
  2Mbit allot 1514 weight 200Kbit prio 5 maxburst 20 avpkt 1000 \
  bounded

# tc qdisc add dev eth1 parent 20:100 sfq quantum 1514b perturb 15
# tc qdisc add dev eth1 parent 20:200 sfq quantum 1514b perturb 15

# tc filter add dev eth1 parent 20:0 protocol ip prio 100 u32 match ip src \
  150.151.23.24 flowid 20:200

# tc filter add dev eth1 parent 20:0 protocol ip prio 25 u32 match ip src \
  150.151.0.0/16 flowid 20:100

8.3 如何處理頻寬透支 (What to do with excess bandwidth)

在我們這個虛構範例中﹐我們發現一個現象﹐就是在 ISP 客戶大都離線的時候(比方說﹐早上 8 點)﹐我們的辦公室卻只有 2Mbit﹐顯然是極其浪費的。

如果將 'bounded' 敘述拿掉﹐那麼該類別就能夠借取其它類別的頻寬來用。

而有些類別或許不願意將他們的頻寬外借﹐例如兩個租用同一線路的敵對 ISP﹐是絕對不會向對方進貢的。在那樣的狀況下﹐您可以在 'tc class add' 的句子後端﹐加上一個關鍵詞 'isolated' 即可。

8.4 類別再分 (Class subdivisions)

FIXME﹕此構思並未完全實測過﹗小心嘗試﹗

我們好不容易殺到這裡﹐事實上還有乘勝追擊的餘地。假設﹐所有辦公室員工同時開啟他們的股票程式﹐這樣還是有可能把資料庫的頻寬給吃掉的。所以﹐我們可以再建兩個子類別﹕'Human' 和 'Database'。

我們的資料庫最少需要 500Kbit﹐那就剩下 1.5M 給我們的員工來揮霍了。

我們要在 Office 這個類別裡面再建兩個類別﹕

# tc class add dev eth0 parent 10:200 classid 10:250 cbq bandwidth 10Mbit rate \
  500Kbit allot 1514 weight 50Kbit prio 5 maxburst 20 avpkt 1000 \
  bounded

# tc class add dev eth0 parent 10:200 classid 10:251 cbq bandwidth 10Mbit rate \
  1500Kbit allot 1514 weight 150Kbit prio 5 maxburst 20 avpkt 1000 \
  bounded

FIXME﹕尚需範例﹗

8.5 多界面的下載分流 (Loadsharing over multiple interfaces)

FIXME﹕整理 TEQL 文件


Next Previous Contents