雲邊通訊中如何對邊緣節點進行流量整形?
1、問題背景
之前專案需要一個邊緣叢集場景下的映象快取功能,但隨之而來的問題是:
由於邊緣節點網路頻寬資源有限,加上雲邊網路的不穩定性,當邊緣節點頻繁拉取雲端映象檔案時會佔用大量的網路頻寬,這樣會阻礙其他網路應用程式的執行,比如此時我們的互動式SSH會話可能會變得異常遲鈍以至於無法使用。因此我們需要一種方法來對高頻寬應用進行流量整形。
目前可以根據 image id、container id來對特定容器進行頻寬限制,下一步準備在k8s環境中進行運用。
程式碼地址:YRXING/bandwidth-limit
2、高效Linux限流神器——Trickle
如果在容器內使用trickle工具,加上--cap-add=NET_ADMIN
2.1、安裝與使用
yum install trickle #原始碼編譯安裝 wget https://codeload.github.com/mariusae/trickle/zip/master mv master trickle-master.zip unzip trickle-master.zip yum -y install autoconf automake libtool libevent-devel autoreconf -if ./configure make make install #使用時,僅需要放在你想要執行的命令之前 trickle -d <download-rate> -u <update-rate> <command> #單位 KB/s #trickle還可以以守護程序形式啟動,它可以限制通過trickle啟動的所有程式的總頻寬使用 trickle -d 1000
2.2、trickle如何工作的
Trickle通過控制socket資料讀寫量來控制和限制應用的上傳/下載速度。它使用另一個版本的BSD套接字API,但區別就是trickle還管理socket呼叫。
但是要注意的是trickle使用動態連結和載入,所以它只對於使用glibc庫
的程式有用。由於trickle可以設定資料在socket上的傳輸延遲,所以它可以用來限制一個應用的網路頻寬。
Trickle通過在程式執行時,預先載入一個速率限制 socket 庫 的方法,trickle 命令允許你改變任意一個特定程式的流量。trickle 命令有一個很好的特性是它僅在使用者空間中執行,這意味著,你不必需要 root 許可權就可以限制一個程式的頻寬使用。要能使用 trickle 程式控制程式的頻寬,這個程式就必須使用非靜態連結庫的套接字介面。當你想對一個不具有內建頻寬控制功能的程式進行速率限制時,trickle 就派上用場了。
socket程式設計中,可以通過修改傳送和接收緩衝區的大小,完全利用可用的頻寬。
int ret, sock, sock_buf_size;
sock = socket( AF_INET, SOCK_STREAM, 0 );
sock_buf_size = BDP;
ret = setsockopt( sock, SOL_SOCKET, SO_SNDBUF,
(char *)&sock_buf_size, sizeof(sock_buf_size) );
ret = setsockopt( sock, SOL_SOCKET, SO_RCVBUF,
(char *)&sock_buf_size, sizeof(sock_buf_size) );
3、控制網絡卡頻寬——wondershaper
3.1、安裝與使用
yum install wondershaper
#使用
wondershaper <interface> <download-rate> <update-rate> #單位KB/s
#比如限制eth0網絡卡頻寬
wondershaper etho 1000 500
#解除限制
wondershaper clear etho
wondershaper實際上是一個shell指令碼,它使用tc來定義流量調整命令,使用QoS來處理特定的網路介面。外發流量通過放在不同優先順序的佇列中,達到限制傳出流量速率的目的,而傳入流量通過丟包的方式來達到速率限制的目的。
4、Linux自帶高階流控——tc
Linux作業系統中的流量控制器TC(Traffic Control)用於Linux核心的流量控制,主要是通過在輸出埠處建立一個佇列來實現流量控制。tc屬於 iproute2
可以直接修改核心的流控設定。
iproute2簡介
iproute2是linux下管理控制TCP/IP網路和流量控制的新一代工具包,旨在替代老派的工具鏈net-tools,即大家比較熟悉的ifconfig,arp,route,netstat等命令。
要說這兩套工具本質的區別,應該是net-tools是通過procfs(/proc)和ioctl系統呼叫去訪問和改變核心網路配置,而iproute2則通過netlink套接字介面與核心通訊。
其次,net-tools的用法給人的感覺是比較亂,而iproute2的使用者介面相對net-tools來說相對來說,更加直觀。比如,各種網路資源(如link、IP地址、路由和隧道等)均使用合適的物件抽象去定義,使得使用者可使用一致的語法去管理不同的物件
Linux流量控制主要分為建立佇列、建立分類和建立過濾器三個方面。
4.1、原理
它以qdisc-class-filter
的樹形結構來實現對流量的分層控制
qdisc
通過佇列將資料包快取起來,用來控制網路收發的速度class
用來表示控制策略filter
用來將資料包劃分到具體的控制策略中
類(Class)組成一個樹,每個類都只有一個父類,而一個類可以有多個子類。某些QDisc(例如:CBQ和HTB)允許在執行時動態新增類,而其它的QDisc(例如:PRIO)不允許動態建立類。允許動態新增類的QDisc可以有零個或者多個子類,由它們為資料包排隊。此外,每個類都有一個葉子QDisc,預設情況下,這個葉子QDisc使用pfifo的方式排隊,我們也可以使用其它型別的QDisc代替這個預設的QDisc。而且,這個葉子QDisc有可以分類,不過每個子類只能有一個葉子QDisc。 當一個數據包進入一個分類QDisc,它會被歸入某個子類。 我們可以使用以下三種方式為資料包歸類,不過不是所有的QDisc都能夠使用這三種方式:
- tc過濾器(tc filter): 如果過濾器附屬於一個類,相關的指令就會對它們進行查詢。過濾器能夠匹配資料包頭所有的域,也可以匹配由ipchains或者iptables做的標記。
- 服務型別(Type of Service): 某些QDisc有基於服務型別(Type of Service,ToS)的內建的規則為資料包分類。
- skb->priority: 使用者空間的應用程式可以使用SO_PRIORITY選項在skb->priority域設定一個類的ID。 樹的每個節點都可以有自己的過濾器,但是高層的過濾器也可以直接用於其子類。 如果資料包沒有被成功歸類,就會被排到這個類的葉子QDisc的隊中。
4.2、應用
-
針對埠進行限速
#檢視現有的佇列 tc -s qdisc ls dev eth0 #檢視現有的分類 tc -s class ls dev eth0 #建立佇列 tc qdisc add dev eth0 root handle 1:0 htb default 1 #新增一個tbf佇列,繫結到eth0上,命名為1:0 ,預設歸類為1 #handle:為佇列命名或指定某佇列 #建立分類 tc class add dev eth0 parent 1:0 classid 1:1 htb rate 10Mbit burst 15k #為eth0下的root佇列1:0新增一個分類並命名為1:1,型別為htb,頻寬為10M #rate: 是一個類保證得到的頻寬值.如果有不只一個類,請保證所有子類總和是小於或等於父類. #ceil: ceil是一個類最大能得到的頻寬值. #建立一個子分類 tc class add dev eth0 parent 1:1 classid 1:10 htb rate 10Mbit ceil 10Mbit burst 15k #為1:1類規則新增一個名為1:10的類,型別為htb,頻寬為10M #為了避免一個會話永佔頻寬,新增隨即公平佇列sfq. tc qdisc add dev eth0 parent 1:10 handle 10: sfq perturb 10 #perturb:是多少秒後重新配置一次雜湊演算法,預設為10秒 #sfq,他可以防止一個段內的一個ip佔用整個頻寬 #使用u32建立過濾器 tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip sport 22 flowid 1:10 #刪除佇列 tc qdisc del dev eth0 root 配置完成後加入本地啟動檔案: /etc/rc.local
-
針對ip進行限速:
情景: 因為頻寬資源有限(20Mbit≈2Mbyte),使用git拉取程式碼的時候導致頻寬資源告警,所以對git進行限速,要求:內網不限速;外網下載速度為1M左右。(注意:此處需要注意單位轉換1byte=8bit)...
#!/bin/bash #針對不同的ip進行限速 #清空原有規則 tc qdisc del dev eth0 root #建立根序列 tc qdisc add dev eth0 root handle 1: htb default 1 #建立一個主分類繫結所有頻寬資源(20M) tc class add dev eth0 parent 1:0 classid 1:1 htb rate 20Mbit burst 15k #建立子分類 tc class add dev eth0 parent 1:1 classid 1:10 htb rate 20Mbit ceil 10Mbit burst 15k tc class add dev eth0 parent 1:1 classid 1:20 htb rate 20Mbit ceil 20Mbit burst 15k #避免一個ip霸佔頻寬資源(1有講到) 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 #建立過濾器 #對所有ip限速 tc filter add dev eth0 protocol ip parent 1:0 prio 2 u32 match ip dst 0.0.0.0/0 flowid 1:10 #對內網ip放行 tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip dst 12.0.0.0/8 flowid 1:20
-
控制出口流量
基於 classless 佇列,我們可以進行故障模擬,也可以用來限制頻寬。TC 使用 linux network netem 模組進行網路故障模擬。
#模擬網路延遲 tc qdisc add dev eth0 root netem delay 100ms #新增一個固定延遲到本地網絡卡eth0 tc qdisc change dev eth0 root netem delay 100ms 10ms #延遲有10ms的上下波動 tc qdisc change dev eth0 root netem delay 100ms 10ms 25% #新增25%的相關概率 tc qdisc change dev eth0 root netem delay 100ms 10ms distribution normal #讓波動成正太分佈 #模擬網路丟包 tc qdisc change dev eth0 root netem loss 1% #丟包率1% tc qdisc change dev eth0 root netem loss 1% 25% #當前丟包概率與上一條資料包丟包概率有25%的相關性 #模擬資料包重複 tc qdisc change dev eth0 root netem duplicate 1% #模擬資料包損壞 tc qdisc change dev eth0 root netem corrupt 2% #模擬資料包亂序 tc qdisc change dev eth0 root netem gap 5 delay 10ms #沒5th的包延遲10ms tc qdisc change dev eth0 root netem delay 10ms reorder 25% 50% # 25%的立刻傳送(50%的相關性),其餘的延遲 10ms
-
控制入口流量
使用 TC 進行入口限流,需要把流量重定向到 ifb 虛擬網絡卡,然後在控制 ifb 的輸出流量
# 開啟 ifb 虛擬網絡卡 modprobe ifb numifbs=1 ip link set ifb0 up # 將 eth0 流量重定向到 ifb0 tc qdisc add dev eth0 ingress handle ffff: tc filter add dev eth0 parent ffff: protocol ip prio 0 u32 match u32 0 0 flowid ffff: action mirred egress redirect dev ifb0 # 然後就是限制 ifb0 的輸出就可以了 # ......
-
監視
#顯示佇列的情況 tc [-s] qdisc|class|filter| ls dev eth0 # -s 顯示詳細資訊
4.3、佇列規則Qdisc詳解
無分類的qdisc:只能用於root佇列
-
[p|b]fifo:簡單先進先出(p是package b是byte)
-
pfifo_fast:根據資料包的tos將佇列劃分到3個band,核心會檢查資料包的TOS欄位,將“最小延遲”的包放到band0。每個band內部先進先出,一個網路介面上如果沒有設定QDisc,pfifo_fast就作為預設的QDisc。
不要將
pfifo_fast qdisc
與後面介紹的PRIO qdisc
混淆,後者是 classful 的! 雖然二者行為類似,但pfifo_fast
是無類別的,這意味你無法通過tc
命令向pfifo_fast
內新增另一個 qdisc。 -
TBF:對於沒有超過預設速率的流量直接透傳,但也能容忍超過預設速率的短時抖動。如果想對一個網絡卡限速,TBF是第一選擇。
在實際實現中,token基於位元組數,而不是包數。下面是一些可配置的引數:
limit or latency: limit是因等待token而被放入佇列的位元組數,latency是每個包在TBF中停留的時間。limit基於latency、bucket size、rate、 peakrate來計算。 burst/buffer/maxburst: bucket的大小,單位是位元組。總體來說,越大的rates就需要越大的緩衝區。要在intel網絡卡上實現10mbit/s整流,至少需要10kbyte緩衝區,最小緩 衝區的大小可以通過rate/HZ來計算得到。如果緩衝區太小,可能會丟包,因為 token 到來太快導致無法放入 bucket 中。 mpu: 最小包單元,對於乙太網,任何一個包的位元組數不會少於64 rate: 流量整形速率 peakrate: 預設情況下,包到了之後只要有 token 就會被立即傳送。這可能不是你期 望的,尤其當 bucket 很大的時候。peakrate 可指定 bucket 傳送資料的最快速度 mtu/minburst: 計算最大可能的 peakrate 時,用 MTU 乘以 100(更準確地說,乘以 HZ 數,例如 Intel 上是 100,Alpha 上是 1024)
-
SFQ:按照會話對流量排序並迴圈傳送每個會話的資料包,SFQ將TCP和UDP流量平均的分配至多個FIFO佇列中,每個佇列對應一個或多個會話,然後按照輪詢的方法依次從每個FIFO佇列中取資料傳送。保證資料傳送的公平性。這種方式保證每一個會話都不會被其他會話所淹沒,會話通過雜湊演算法對映到某個佇列裡去。
SFQ 只有在實際出向頻寬已經非常飽和的情況下才有效,這一點非常重要!說的更明確一點:沒用配套的整流配置的話,單純在(連線 modem 的)乙太網介面上配置SFQ 是毫無意義的。
引數和用法:
perturb: 每隔多少秒就重新配置雜湊演算法,10s是個不錯的選擇。 quantum: 當前queue允許出隊的最大位元組數,預設是一個MTU。 limit: SFQ能快取的最大包數。
使用建議
- 單純對出口流量限速使用TBF,針對大頻寬進行限速,需要將bucket調大。
- 如果頻寬已經被打滿,想確保頻寬沒有任何單個session佔據,推薦使用SFQ。
- 如果你不需要整形,只是想看看網路介面是否過載,使用pfifo(內部沒有bands,但會記錄backlog的大小)。
有分類的qdisc(可以包括多個佇列)
如果想對不同的流量做不同的處理,那麼classful qdisc非常有用。
-
CBQ:基於類的排隊規則是最複雜最花哨的,CBQ 的工作原理是:在傳送包之前等待足夠長的時間,以將頻寬控制到期望 的閾值。為實現這個目標,它需要計算包之間的等待間隔。由於它與Linux的工作方式不是太匹配,導致它的演算法不精確。
相關引數:
avpkt: 平均包長,單位是位元組,計算maxidle會用到 bandwidth: 裝置物理頻寬,計算idle time會用到 cell: 包長的增長步長,預設值是8,必須是2的冪。 maxburst: 計算 maxidle 時用到,單位:包數(number of packets)。 當 avgidle == maxidle 時,可以併發傳送 maxburst 個包,直到 avgidle == 0。 注意 maxidle 是無法直接設定的,只能通過 這個引數間接設定。 minburst: 前面提到,overlimit 情況下 CBQ 要執行 throttle。理想情況下是精確 throttle calculated idel time,然後傳送一個包。 但對 Unix 核心來說,通常很難排程 10ms 以下精度的事件,因此最好的方式就是 throttle 更長一段時間,然後一次發 送 minburst 個包,然後再睡眠 minburst 倍的時間。 從較長時間跨度看,更大的 minburst 會使得整形更加精確,但會導致在毫秒級別有更大的波動性。 minidle: 如果 avgidle < 0,那說明 overlimits,需要等到 avgidle 足夠大才能傳送下一個包。 為防止突然的 burst 打爆鏈路頻寬,當 avgidle 降到一個非常小的值之後,會 reset 到 minidle。minidle單位是負微秒 mpu: 最小包長 rate: 期望離開這個qdisc的流量速率
除了整形之外,CBQ 也能完成類似
PRIO
queue 的功能 。每次硬體層請求一個數據包來發送時,都會開啟一個 weighted round robin (WRR)過程, 從優先順序最高的 class 開始(注意,優先順序越高對應的 priority number 越小)allot: 每次輪到一個class時傳送的資料量大小 prio: 內部的classes都有一個優先順序prio weight: 這個引數用於 WRR 過程。每個 class 都有機會發送資料。如果要指定某個 class 使用更大的頻寬,就調大其 weight CBQ 會將一個 class 內的所有權重歸一化,因此指定用整數還是小數都沒關係:重要的是比例。大家的經驗值是 “rate/10”,這個值 看上去工作良好。歸一化後的 weight 乘以 allot,決定了每次能傳送的資料量
除了限制特定型別的流量,還能指定哪些 class 能從另外哪些 class 借容量(borrow capacity)或者說,借頻寬
isolated/sharing: 配置了 isolated 的 class 不會向 sibling classes 借出頻寬,sharing作用相反 bounded/borrow: 也可以配置 class 為 bounded,這表示它不會向其他 siblings 借頻寬,borrow作用相反
示例配置:
# webserver 限制為 5Mbps # SMTP 流量限制為 3Mbps # webserver + SMTP 總共不超過6Mbps # 無力網絡卡是100Mbps # 每個class之間可以互借寬頻 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:1class是bounded型別,因此總頻寬不會超過設定的6Mbps限制。 tc class add dev eth0 parent 1:1 classid 1:3 cbq bandwidth 100Mbit \ rate 5Mbit wight 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 wight 0.3Mbit prio 5 allot 1514 cell 8 maxburst 20 avpkt 1000 tc qdisc add dev eth0 parent 1:3 handle 30: sfq tc qdisc add dev eth0 parent 1:4 handle 40: sfq #這些過濾規則直接作用在root qdisc,作用是將流量分類到下面正確的qdisc 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 sprot 25 0xffff flowid 1:4
沒有匹配到以上兩條規則的流量會進入1:0接受處理,而這裡是沒有限速的。
如果SMTP+web的總頻寬超過6Mbps,那總頻寬將根據給定的權重引數分為兩部分,5/8給webserver,3/8給郵件服務。也就是說webserver在任何情況下至少能獲得5/8 * 6Mbps = 3.75Mbps頻寬。
-
HTB:由於CBQ太複雜,devik設計了HTB。
適用場景:
有一個固定總頻寬,想將其分割成幾個部分,分別用作不同的目的;每個部分的頻寬是保證的;還可以指定每個部分向其他部分借頻寬。
HTB工作方式與CBQ類似,但不是藉助於計算空閒時間來實現整形,在內部,它其實是一個 classful TBF。
示例配置:
# 功能和前面CBQ配置一樣的HTB配置 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 #htb作者推薦在這些class內部適用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 #將流量導向這些class的過濾器 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
未分類的流量會進入30: 這個band頻寬很小,但能夠從剩餘的可用頻寬中借頻寬來用。
-
PRIO:優先順序排隊規則實際上並不進行整形,它僅僅根據你配置的過濾器把流量分類,預設會自動建立三個
FIFO
類。(它不允許動態新增類)prio像是pfifo_fast的升級版,它也有多個band,但每個band都是一個class,如果想基於tc filters而不僅僅是TOS flags做流量的優先順序分類,這個qdisc非常有用。由於prio沒有流量整形的功能,因此針對SFQ的忠告也適用於這裡。
相關引數:
bands: 需要建立的band數量 priomap: 如果沒有提供 tc filters 來指導如何對流量分類,那 PRIO qdisc 將依據 TC_PRIO 優先順序來決定優先順序
PRIO qdisc 裡面的 band 都是 class,預設情況下名字分別為
major:1
、major:2
、major:3
, 因此如果你的 PRIO qdisc 是12:
,那 tc filter 送到12:1
的流量就有更高的優先順序。重複一遍:band 0 對應的 minor number 是 1! band 1 對應的 minor number 是 2 ,以此類推。
示例配置:
高吞吐量流量將會進入優先順序比較低的30:這個band,而互動式流量會進入優先順序更高的bands 10:或20:
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
4.4、用過濾器對流量進行分類
class
用來表示控制策略,只用於有分類的qdisc
上。每個class
要麼包含多個子類,要麼只包含一個子qdisc
。當然,每個class
還包括一些列的filter
,控制資料包流向不同的子類,或者是直接丟掉。
filter
用來將資料包劃分到具體的控制策略中,filter
是在qdisc內部。包括以下幾種:
-
u32
:根據協議、IP
、埠等過濾資料包# 匹配源/目的IP match ip src/dst 1.2.3.0/24 # 匹配源/目的埠,任何IP協議 match ip sport/dport 80 0xffff # 匹配ip protocol(tcp udp icmp gre ipsec) # 使用/etc/protocols裡面的協議號,例如ICMP是1: match ip protocol 1 0xffff
-
fwmark
:根據iptables MARK
來過濾資料包# 可以用ipchains/iptables等工具對包打標(mark),這些mark在不同介面之間路由時是不會丟失的 # 例如:只對從eth0進入eth1的流量進行整形的功能 tc filter add dev eth1 protocol ip parent 1: prio 1 handle 6 fw flowid 1:1 iptables -A PREROUTING -t mangle -i eth0 -j MARK --set-mark 6 #下面的命令會列印 mangle 表內所有的 mark 規則,已經每個規則已經匹配到多少包和位元組數: iptables -L -t mangle -n -v
-
tos
:根據tos
欄位過濾資料包#選擇互動式、最小延遲流量: tc filter add dev ppp0 parent 1: protocol ip prio 10 u32 match ip tos 0x10 0xff flowid 1:4 #高吞吐流量(bulk traffic)對應的過濾條件是 0x08 0xff
當enqueue一個包時,在每一個分叉的地方都需要查詢相關的過濾規則。
一種配置是:在1:1配置一個filter,將包送到12: 。在12:配置一個filter,將包送到12:2
另一種配置是將兩個filter都配置在1:1,但將更精確的filter放到更下面的位置有助於提升效能。
需要注意的是,包是無法向上過濾的(filter a packet ‘upwards’)。 另外,使用 HTB 時,所有的 filters 必須 attach 到 root
!
示例配置:
# 假設一個名為10:的PRIO qdisc,我們想將埠22的流量都導向優先順序最高的band
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 sprot 80 0xffff flowid 10:1
tc filter add dev eth0 protocol ip parent 10: prio 2 flowid 10:2 #為匹配上面的包都發送到優先順序次高的band 10:2
# 精確匹配單個IP地址
tc filter add dev eth0 parent 10: protocol ip prio 1 u32 match ip dst 4.3.2.1/32 flowid 10:1
tc filter add dev eth0 parent 10: protocol ip prio 1 u32 match ip src 1.2.3.4/32 flowid 10:1
tc filter add dev eth0 parent 10: protocol ip prio 2 flowid 10:2
#這會將 dst_ip=4.3.2.1 或 src_ip=1.2.3.4 的流量送到優先順序最高的佇列,其他流量送到優先順序次高的佇列
#還可以將多個match級聯起來,同時匹配源IP和port
tc filter add dev eth0 parent 10: protocol ip prio 1 u32 match ip port src 4.3.2.1/32 \
match ip port 80 0xffff flowid 10:1
4.5、ingress shaping
IMQ
中介佇列裝置用來解決兩個侷限:
- 只能進行出口整形
- 一個佇列規定只能處理一塊網絡卡的流量,無法設定全域性的限速
對外部進來的包先打上標記(mark),打了標的包在經過 netfilter NF_IP_PRE_ROUTING
和 NF_IP_POST_ROUTING
hook 點時會被捕獲,送到 IMQ 裝置上 attach 的 qdisc。就能實現入向整型(ingress shaping);將介面們作為 classes(treat interfaces as classes),就能設定全侷限速。
示例配置:
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
接下來選中流量,給他們打上標記,以便被正確送到imq0
裝置:
iptabels -t mangle -A PREROUTING -i eth0 -j IMQ --todev 0
ip link set imq0 up
ifb——見5.3
5、Linux網路測試工具
5.1、實時網路流量監控——iftop
Iftop工具主要用來顯示本機網路流量情況及各相互通訊的流量集合,如單獨同哪臺機器間的流量大小,非常適合於代理伺服器和iptables伺服器使用,這樣可以方便的檢視各客戶端流量情況。iftop可以用來監控網絡卡的實時流量(可以指定網段)、反向解析IP、顯示埠資訊等,詳細的將會在後面的使用引數中說明。
# 安裝所需依賴包
yum -y install flex byacc libpcap ncurses ncurses-devel libpcap-devel
wget http://www.ex-parrot.com/pdw/iftop/download/iftop-0.17.tar.gz #下載安裝包
tar zxvf iftop-0.17.tar.gz #解壓
cd iftop-0.17
./configure --prefix=/usr/local/iftop #配置安裝目錄
make && make install #編譯、安裝
cp /usr/local/iftop /usr/local/bin
- 預設使用第一個網路介面
eth0
- 預設顯示
rates
的3列資料分別表示:最近2秒,10秒和40秒的平均流量。 peak
指網路速率的尖峰值(最大)cum
表示累積流量cumulative
,在互動介面時按下T
就可以看到主機對
之間累計的網路資料流量
互動介面操作
T
- 顯示或隱藏累積網路資料量(cumulative
),會在顯示主機對的3列網路速率rate
左邊再增加一列顯示累積資料量cum
S
- 顯示源端埠D
- 顯示目的端埠n
- 顯示主機IP地址而不是解析的主機名1/2/3
- 按照指定列進行排序<
- 根據源名字排序>
- 根據目的名字排序P
- 暫停顯示(否則就不斷更新當前顯示)j/k
- 滾動顯示?
- 幫助
過濾和排序&啟動引數
在互動模式,按下l
(表示limit
)會在頂端顯示一個文字輸入框,可以輸入過濾規則,只顯示特定內容。
iftop大多數啟動引數和互動介面的快捷鍵相關。
使用-f
引數是一種過濾特性資料包的方法,可以組合網路,主機或埠。例如以下只顯示在/dev/wlan0無限網絡卡介面的ssh資料包
iftop -i wlan0 -f "dst port 22"
過濾器 | 描述 |
---|---|
dst host 1.2.3.4 | 所有目標地址是1.2.3.4的資料包 |
src port 22 | 所有從埠22發出的資料包 |
dst portrange 22-23 | 埠範圍是22到23的資料包 |
gateway 1.2.3.5 | 使用閘道器地址1.2.3.5的資料包 |
5.2、頻寬測試工具——iperf3
Iperf可以測試最大TCP和UDP頻寬效能,具有多種引數和UDP特性,可以根據需要調整,可以報告頻寬、延遲抖動和資料包丟失.對於每個測試,它都會報告頻寬,丟包和其他引數,可在Windows、Mac OS X、Linux、FreeBSD等各種平臺使用,是一個簡單又實用的小工具。
Iperf3也是C/S(客戶端/伺服器端)架構模式,在使用iperf3測試時,要同時在server端與client端都各執行一個程式,讓它們互相傳送報文進行測試。
yum install -y iperf3
服務端命令列
-s 表示伺服器端;
-p 定義埠號;
-i 設定每次報告之間的時間間隔,單位為秒,如果設定為非零值,就會按照此時間間隔輸出測試報告,預設值為零
客戶端命令列
-c 表示伺服器的IP地址;
-p 表示伺服器的埠號;
-t 引數可以指定傳輸測試的持續時間,Iperf在指定的時間內,重複的傳送指定長度的資料包,預設是10秒鐘.
-i 設定每次報告之間的時間間隔,單位為秒,如果設定為非零值,就會按照此時間間隔輸出測試報告,預設值為零;
-w 設定套接字緩衝區為指定大小,對於TCP方式,此設定為TCP視窗大小,對於UDP方式,此設定為接受UDP資料包的緩衝區大小,限制可以接受資料包的最大值.
--logfile 引數可以將輸出的測試結果儲存至檔案中.
-J 來輸出JSON格式測試結果.
-R 反向傳輸,預設iperf3使用上傳模式:Client負責傳送資料,Server負責接收;如果需要測試下載速度,則在Client側使用-R引數即可.
-u 採用udp協議
6、實驗過程
實驗一共兩個節點,IP為172.16.9.3/172.16.9.5。通過改變5號機的tc策略,將其頻寬限制在某個值以內,然後用3號機測試上傳速度是否符合預期。本次實驗主要測試tc的入口流量限制能否有效。
先看一下兩個節點之間原本的頻寬。
#3號機
iperf3 -s -p 80
#5號機
ieprf3 -c 172.16.9.3 -p 80 -R #測試下載速度
現在限制5號機入口流量
#按照前面方法將流量重定向到ifb0網絡卡
tc qdisc add dev ifb0 root tbf rate 100mbit latency 50ms burst 1600
可以看到頻寬為80Mbit,雖然有誤差,但已經達起到了限制入口流量的作用。經過除錯發現這麼大的誤差跟burst引數值的設定有關,我索性增加10倍,改為16000。
讓我們多測幾次看一看誤差有多少,發現只要burst值設定的夠大,基本有4%-5%的誤差,可以接受。
限制頻寬 | 實際頻寬 | Burst | 誤差 |
---|---|---|---|
10mbit | 9.67mbit | 16000 | 3.3% |
100mbit | 96mbit | 16000 | 4% |
200mbit | 142mbit | 1600 | 29% |
350mbit | 336mbit | 160000 | 4% |
350mbit | 333mbit | 16000 | 4.9% |
500mbit | 478mbit | 16000 | 4.4% |
7、拓展
7.1、百度網盤是怎麼限速的?
百度網盤是在伺服器端做了限速,控制傳送資料的頻率來實現,而pandownload破解限速的原理就是開啟多執行緒。
7.2、QoS
服務質量(英語:Quality of Service,縮寫QoS)是一個術語,在分組交換網路領域中指網路滿足給定業務合約的機率;或在許多情況下,非正式地指分組在網路中兩點間通過的機率。QoS是一種控制機制,它提供了針對不同使用者或者不同資料流採用相應不同的優先順序,或者是根據應用程式的要求,保證資料流的效能達到一定的水準。QoS的保證對於容量有限的網路來說是十分重要的,特別是對於流多媒體應用,例如VoIP和IPTV等,因為這些應用常常需要固定的傳輸率,對延遲也比較敏感。
分組排序和頻寬分配的QoS機制分別是排隊和頻寬管理。然而,在實施之前,必須使用分類工具區分流量。根據策略對流量進行分類使組織能夠為其最重要的應用程式確保資源的一致性和足夠的可用性。
流量可以按埠或 IP 進行粗略分類,也可以使用更復雜的方法(例如按應用程式或使用者)進行分類。後面的引數允許更有意義的識別,並因此對資料進行分類
7.3、ifb虛擬網絡卡是什麼?
Linux TC是一個控發不控收的框架,然而這是對於TC所置於的位置而言的,而不是TC本身的限制,事實上,你完全可以自己在ingress點上實現一個佇列機制,說TC控發不控收只是因為Linux TC目前的實現沒有實現ingress佇列而已。
ifb驅動模擬一塊虛擬網絡卡,它可以被看作是一個只有TC過濾功能的虛擬網絡卡,說它只有過濾功能,是因為它並不改變資料包的方向,即對於往外發的資料包被 重定向到ifb之後,經過ifb的TC過濾之後,依然是通過重定向之前的網絡卡發出去,對於一個網絡卡接收的資料包,被重定向到ifb之後,經過ifb的TC 過濾之後,依然被重定向之前的網絡卡繼續進行接收處理,不管是從一塊網絡卡傳送資料包還是從一塊網絡卡接收資料包,重定向到ifb之後,都要經過一個經由ifb 虛擬網絡卡的dev_queue_xmit操作。
除 了ingress佇列之外,在多個網絡卡之間共享一個根Qdisc是ifb實現的另一個初衷,可以從檔案頭的註釋中看出來。如果你有10塊網絡卡,想在這10 塊網絡卡上實現相同的流控策略,你需要配置10遍嗎?將相同的東西抽出來,實現一個ifb虛擬網絡卡,然後將這10塊網絡卡的流量全部重定向到這個ifb虛擬網 卡上,此時只需要在這個虛擬網絡卡上配置一個Qdisc就可以了。
7.4、資料包傳輸過程中用到的各種佇列
驅動程式佇列(又名環形緩衝區)
在IP堆疊和NIC之間是驅動程式佇列,該佇列不包含資料包資料,他由指向稱為套接字核心緩衝區SKB的其他資料結構的描述符組成,這些資料結構儲存資料包資料,並在整個核心中使用。
驅動程式佇列存在的原因是為了確保每當系統有資料要傳輸時,這些資料都可以立即傳輸給 NIC,也就是說它充當生產者-消費者中的快取作用,使得資料的生產和消費非同步起來,提高效率。
來自堆疊的大資料包
大多數NIC都有一個固定的最大傳輸單元MTU,對於乙太網預設為1500位元組。如果應用程式向TCP套接字寫入2000位元組,則IP堆疊需要建立兩個IP資料包並通過驅動程式佇列傳輸,更多的資料包意味著更多的網路協議頭部開銷。為了避免這種情況,Linux核心實施了很多優化:TCP分段解除安裝(TSO)、UDP分段解除安裝(UFO)和通用分段解除安裝(GSO)。
當啟用 TSO、UFO 或 GSO 時,可以將大資料包傳送到 NIC。這會大大增加驅動程式佇列中的位元組數。得注意的是,Linux 還具有接收端優化,其操作類似於 TSO、UFO 和 GSO。這些優化的目的還在於減少每個資料包的開銷。具體來說,通用接收解除安裝 ( GRO ) 允許 NIC 驅動程式將接收到的資料包組合成一個大資料包,然後將其傳遞到 IP 堆疊。在轉發資料包時,GRO 允許重構原始資料包,這是保持 IP 資料包端到端性質所必需的。然而,有一個方面的影響,當大資料包在轉發操作的傳輸側被分解時,它會導致流的多個數據包同時排隊。資料包的這種“微突發”會對流間延遲產生負面影響。
飢餓和延遲
儘管有其必要性和好處,但 IP 堆疊和硬體之間的佇列引入了兩個問題:飢餓和延遲。
如果NIC驅動程式喚醒以從佇列中拉出資料包進行傳輸,並且佇列為空,則硬體將錯過傳輸機會,從而降低系統的吞吐量。這被稱為飢餓。如果在一個繁忙的系統中,IP堆疊將獲得更少的機會把資料包新增到緩衝區,那麼硬體很可能在新的資料包到來之前消耗完緩衝區,因此需要一個大的緩衝區來減少飢餓的可能並確保高吞吐量,但它的缺點是引入大量延遲。
上圖驅動程式佇列中,幾乎充滿了高頻寬,大容量的TCP段,排在最後的是來自 VoIP 或遊戲流的資料包(黃色)。VoIP 或遊戲等互動式應用程式通常以固定間隔發出小資料包,這些資料包對延遲敏感,而高頻寬資料傳輸會產生更高的資料包速率和更大的資料包。這種較高的資料包速率可以填充互動資料包之間的緩衝區,從而導致互動資料包的傳輸延遲。
因此,為驅動程式佇列選擇合適的大小是一個“Goldilocks problem”。
位元組佇列限制BQL
位元組佇列限制 (BQL) 是最近的 Linux 核心 (> 3.3.0) 中的一項新功能,它試圖自動解決驅動程式佇列大小的問題。這是通過新增一個層來實現的,該層基於計算在當前系統條件下避免飢餓所需的最小緩衝區大小來啟用和禁用對驅動程式佇列的排隊。回想之前,排隊資料量越小,排隊資料包所經歷的最大延遲越低。
理解 BQL 不會改變驅動程式佇列的實際大小是關鍵。而是 BQL 計算當前時間可以排隊的資料量(以位元組為單位)的限制。超過此限制的任何位元組都必須由驅動程式佇列上方的層保留或丟棄。
BQL 基於測試裝置是否處於飢餓狀態。如果它被餓死,那麼 LIMIT 會增加,允許更多的資料排隊,從而減少餓死的機會。如果裝置在整個時間間隔內都處於忙碌狀態,並且佇列中仍有位元組要傳輸,則佇列大於當前條件下系統所需的佇列,並且減少 LIMIT 以限制延遲,這個機制有點類似於TCP的擁塞控制機制。
BQL 通過將排隊資料量限制為避免飢餓所需的最低限度來減少網路延遲。它還具有非常重要的副作用,將大多數資料包排隊的點從驅動程式佇列(一個簡單的 FIFO)移動到佇列規則 (QDisc) 層,該層能夠實現更復雜的排隊策略。
排隊規則Qdisc
驅動程式佇列是一個簡單的先進先出 (FIFO) 佇列。它平等地對待所有資料包,並且沒有區分不同流的資料包的能力。這種設計使 NIC 驅動程式軟體保持簡單和快速。夾在 IP 堆疊和驅動程式佇列之間的是排隊規則 (QDisc) 層。該層實現了 Linux 核心的流量管理功能,包括流量分類、優先順序和速率整形。
預設情況下,每個網路介面都分配了一個pfifo_fast QDisc,它基於 TOS 位實現了一個簡單的三頻段優先順序方案。類和過濾器的概念不在介紹。
傳輸層和排隊規則之間的緩衝
在檢視前面的圖時,您可能已經注意到在排隊規則層之上沒有資料包佇列。這意味著網路資料包直接放入排隊規則中,對於具有單個佇列的 QDisc,會出現上圖中針對驅動程式佇列概述的相同問題。也就是說,單個高頻寬或高資料包速率流會消耗佇列中的所有空間,從而導致資料包丟失並給其他流增加顯著的延遲。
從 Linux 3.6.0 (2012-09-30) 開始,Linux 核心有一個名為 TCP Small Queues 的新功能,旨在解決 TCP 的這個問題。
另一個部分解決方案是使用一個 QDisc,它有許多佇列,理想情況下每個網路流一個。隨機公平佇列 ( SFQ ) 和具有受控延遲的公平佇列 ( fq_codel ) QDisc 都很好地解決了這個問題,因為它們實際上每個網路流都有一個佇列。
如何在Linux中操作佇列大小
ethtool
命令用於控制乙太網裝置驅動程式佇列的大小。ethtool 還提供低階介面統計資訊以及啟用和禁用 IP 堆疊和驅動程式功能的能力。
而BQL演算法是自調整的,因此不需要過多地弄亂它。如果非要修改,那麼需要覆蓋計算出的LIMIT值的上限,根據NIC的位置和名稱,可以在/sys目錄中找到BQL的狀態和配置。在我的伺服器上,eth0目錄是/sys/devices/pci0000:00/0000:00:14.0/net/eth0/queues/tx-0/byte_queue_limits
。
這個目錄下主要的兩個檔案:
- limit_max:LIMIT的可配置最大值,將此值設定得較低以優化延遲
- limit_min:LIMIT的可配置最小值,將此值設定得較高以優化吞吐量
什麼是txqueuelen
Linux 中傳輸佇列的長度預設為 1,000 個數據包,這是一個很大的緩衝,尤其是在低頻寬下。txqueuelen 僅用作某些排隊規則的預設佇列長度。
對於大多數排隊規則,tc命令列上的limit引數會覆蓋txqueuelen的預設值,也可以通過ip命令設定。
ip link set txqueuelen 500 dev eth0
7.5、微服務限流是限制頻寬嗎?
微服務限流是通過對併發/請求進行限速來保護系統,防止系統過載,限流的方式有:
- QPS:限制每秒的請求數
- 併發數:避免開啟過多縣城導致資源耗盡
- 連線數:限制TCP連線數
所以,微服務的限流並不是限制頻寬,而是限制一定時間區間內的併發數量,而常見的限流演算法:漏桶演算法、令牌桶演算法等是針對這個的。Golang標準庫中也自帶了限流演算法的實現golang.org/x/time/rate
,該限流器是基於Token Bucket實現的。
參考連結:
How to limit network bandwidth on Linux
Linux Advanced Routing & Traffic Control HOWTO
Trickle: A Userland Bandwidth Shaper for Unix-like Systems
Queue in the Linux Network Stack