http://support.huawei.com/huaweiconnect/enterprise/thread-279033.html
CIR,CBS,EBS,PIR,PBS傻傻分不清楚?看這裏!----揭祕令牌桶 |
概述
春暖花開的時候,大家都開着汽車外出旅遊欣賞美麗的風景,卻被堵在高速公路上,你是否爲此感到痛苦?但如果有一種機制可以評估高速公路上的車流量、控制車流情況,確保進入高速公路的汽車都能在路上安全暢行,你是不是會覺得很開心?
與此相似,網絡發生擁塞的時候,也是一件非常痛苦的事情,如圖1和圖2所示。
圖1 網絡擁塞場景1
圖2 網絡擁塞場景2
如果不限制用戶發送的業務流量大小,大量不斷突發的業務數據會使網絡更加擁擠,嚴重時會出現網絡擁塞,造成業務出現異常,同時也浪費網絡資源,如圖3和圖4所示。
圖3 網絡擁塞造成資源浪費
圖4 網絡擁塞引起業務異常
那麼,你是否很期待有一種機制可以在網絡上通過監督進入網絡的流量速率,以達到限制流量、提高網絡資源使用效率的目的,從而保證有限的網絡資源提供更好的網絡服務?
流量評估
爲了達到上述目的,我們需要對進入網絡的流量進行監督,實現CAR(Committed Access Rate)。
CAR:將進入網絡的用戶流量的速率限制在約定的範圍之內,從而避免引起網絡擁塞。
要實現CAR,就需要對流量進行評估,然後根據評估的結果對流量採取相應的動作:
l 如果流量沒有超速,設備會爲報文獎勵綠牌(將報文染色爲綠色)。報文可暢通無阻,即被轉發。
l 如果流量稍微超速,設備會發出黃牌警告(將報文染色爲***)。通常報文會被降級,即修改報文的內部優先級,然後進行盡力而爲的轉發。
l 如果流量超速太多,設備會發出紅牌將報文罰下(將報文染色爲紅色)。報文被禁止通行,即丟棄。
然而,報文不像汽車那樣可以通過測速儀之類的儀器進行測速。那麼,如何對報文的速率進行評估呢?——答案在這裏:令牌桶。
令牌桶可以看作是一個存放令牌的容器,預先設定一定的容量。系統按給定的速度向桶中放置令牌,當桶中令牌滿時,多餘的令牌溢出。令牌桶是一種流量測量方法。
不得不說的令牌桶
接着上面高速公路的例子,假設進入高速公路的車輛需要在入口處領取到通行卡才能進入高速公路。爲了節約人力成本,入口處放置自動出卡機。按照國家高速公路交通安全法的規定,在高速公路上行駛的車輛,車速超過100km/h時,應與同車道前車保持100米以上距離。爲了保持最小安全行車距離100米,按車速100km/h計算,需要間隔至少3.6秒才能放行一輛車,因此出卡機每隔3.6秒出一張通行卡。在自動出卡機下放置一個盒子,自動出卡機按照3.6秒的間隔向盒子中投放通行卡。每輛進入高速公路的車輛,從盒子中領取通行卡之後纔可以進入高速公路。
令牌桶算法與此類似。簡單來說,令牌桶可以看作是一個存放一定數量令牌的容器。系統按設定的速度向桶中放置令牌。當桶中令牌滿時,多出的令牌溢出,桶中令牌不再增加。在使用令牌桶對流量規格進行評估時,是以令牌桶中的令牌數量是否足夠滿足報文的轉發爲依據的。每個需要被轉發的報文,都要從令牌桶中領取一定數量的令牌(具體數量視報文大小而定),纔可以被正常轉發。如果桶中存在足夠的令牌可以用來轉發報文,稱流量遵守或符合約定值,否則稱爲不符合或超標。
按照系統向令牌桶投放令牌的速率和令牌桶的數量劃分,令牌桶算法有三種模式:
l 單速單桶
l 單速雙桶
l 雙速雙桶
下面我們以色盲模式爲例詳細介紹這三種模式。
單速單桶
假設有一套自動出卡系統,包括一臺自動出卡機和一個盒子C。自動出卡機以固定的速率(3.6秒出一張通行卡)向盒子中投放通行卡。這個固定的速率,就相當於是單速單桶模式中的CIR參數。
CIR(Committed Information Rate):承諾信息速率,表示向C桶(單桶模式中只有一個令牌桶,稱爲C桶)中投放令牌的速率,即C桶允許傳輸或轉發報文的平均速率。
如果平均每3.6秒來了不止1輛車,通行卡很快就領完了。這時自動出卡機上的紅燈亮起,新到的車輛禁止進入高速公路。這就起到了限定作用,將放行車輛的速率限制在放卡速率範圍內。
如果平均每3.6秒來了不到1輛車,盒子裏就會有一些積累起來的通行卡。這些累積起來的通行卡可以應付車隊(比如車友俱樂部的車隊)要進入高速公路這樣的情況。我們規定每輛車都要領取通行卡,都由領隊的車領取。
假設某時刻來了一個車隊共8輛車,但盒子裏只有6張通行卡,領隊車拿起通行卡一數,發現不夠,那麼這個車隊不允許進入高速,通行卡放回盒子中。
盒子中的通行卡不停累積,總有盒子裝滿的時候。這個盒子的容積,就相當於單速單桶模式中的CBS參數。
CBS(Committed Burst Size):承諾突發尺寸,表示C桶的容量,即C桶瞬間能夠通過的承諾突發流量。相當於盛放通行卡的盒子裏最多可以放多少張通行卡,也就是說,該自動出卡系統允許通過的車隊中最多可以有多少輛車。
在單速單桶模式中,系統按照CIR速率向C桶中投放令牌。
l 如果可用令牌的總數量(Tc)小於CBS,則令牌數繼續增加。
l 如果令牌桶已滿,則令牌數不再增加。
對於到達的報文(報文大小爲B),
l 如果B ≤ Tc,報文被標記爲綠色,且Tc減少B。
l 如果B > Tc,報文被標記爲紅色,Tc不減少。
假設設備端口的CIR設置爲1Mbit/s,CBS爲2000bytes,初始狀態時C桶滿。
說明:爲方便計算,此處1Mbit/s按1*106bit/s計算。
l 假設第1個到達的報文是1500bytes時,檢查C桶發現令牌數大於數據包的長度,所以數據包被標爲綠色,C桶減少令牌1500bytes,還剩500bytes。
l 假設1ms之後到達第2個報文1500bytes。在此間隔內,C桶新增令牌 = CIR * 1ms = 1000bit = 125bytes,加上C桶原來剩餘的令牌500bytes,此時C桶共有625bytes。令牌數量不夠,報文標記爲紅色。
l 假設又過1ms後到達第3個報文1000bytes。在此間隔內,C桶新增令牌125bytes,加上C桶原來剩餘的令牌625bytes,此時C桶共有750bytes。令牌數量不夠,因此報文被標記爲紅色。
l 假設又過20ms後到達第4個報文1500bytes。在此間隔內,C桶新增令牌 = CIR * 20ms = 20000bit = 2500bytes,加上C桶原來剩餘的令牌750bytes,C桶此時令牌數爲3250bytes。而CBS = 2000bytes,因此溢出1250bytes令牌被丟棄。此時C桶令牌數大於報文長度,報文標記爲綠色,C桶減少令牌1500bytes,剩500bytes。
報文處理過程彙總見下表。
包序號 |
時刻 (ms) |
包長 (bytes) |
與上次添加令牌的間隔 |
本輪增加令牌 |
令牌增加後C桶令牌 |
報文處理後C桶剩餘令牌 |
報文標記結果 |
2000 |
2000 |
- |
|||||
1 |
0 |
1500 |
0 |
0 |
2000 |
500 |
綠色 |
2 |
1 |
1500 |
1 |
125 |
625 |
625 |
紅色 |
3 |
2 |
1000 |
1 |
125 |
750 |
750 |
紅色 |
4 |
22 |
1500 |
20 |
2500 |
2000 |
500 |
綠色 |
單速雙桶
在單速單桶模式中說到,如果平均每3.6秒來了不到1輛車,盒子裏就會有一些積累起來的通行卡。如果一直沒有車輛過來,盒子中的通行卡不停地累積。盒子的容量是有限的,當盒子中裝滿通行卡之後,不斷投放的通行卡就溢出盒子,會造成浪費。
爲了避免這種浪費,我們改進了這個自動出卡系統,在原來的基礎上增加一個盒子E(改進後的系統對應單速雙桶模式,盒子E對應單速雙桶中的E桶)。自動出卡機首先向C盒中投放通行卡。當C盒滿了,自動出卡機就向E盒中投放通行卡。
爲了保證通行卡有序領取,我們規定先領取C盒中的通行卡。如果C盒中的通行卡不夠用,就把卡放回C盒,再從E盒中重新領取通行卡。C盒和E盒中的通行卡不能同時取用。
按照通行卡的取用,可以分爲三種情況:
l 如果C盒中的通行卡夠用,綠燈亮,車輛領取C盒中的通行卡後通行。
l 如果C盒中的通行卡不夠用但E盒中的通行卡夠用,黃燈亮,從C盒中領取的通行卡領取的通行卡要歸還,車輛領取E盒中的通信卡後通行。
l 如果E盒中的通行卡也不夠用,紅燈亮,車輛禁止通行,從E盒中領取的通行卡領取通行卡要歸還。
和單速單桶模式一樣,對於車隊,有幾輛車,就領取幾張通行卡。當然,E盒的容量也是有限的。E盒的容量就相當於單速雙桶模式中的EBS。
EBS(Excess Burst Size):超額突發尺寸,表示E桶的容量,即E桶瞬間能夠通過的超出突發流量。
假設某時刻來了一個車隊有8輛車,但C盒裏只有5張通行卡,而E盒中有9張通行卡,那麼黃燈亮起,這輛長車從E盒中領取8張通行卡。
在單速雙桶模式中,系統按照CIR速率向桶中投放令牌。
l 如果C桶中可用令牌的總數量(Tc)小於CBS,則C桶中令牌數增加。
l 如果Tc等於CBS且E桶中的可用令牌總數量(Te)小於EBS,則C桶中令牌數不增加,E桶中令牌數增加。
l 如果C桶和E桶中的令牌都已滿,則兩個桶中的令牌數都不再增加。
對於到達的報文(報文大小爲B),
l 如果B ≤ Tc,報文被標記爲綠色,且Tc減少B。
l 如果Tc < B ≤ Te,報文被標記爲***,且Te減少B,Tc不減少。
l 如果B > Te,報文被標記爲紅色,且Tc和Te都不減少。
假設設備端口的CIR設置爲1Mbit/s,CBS爲2000bytes,EBS爲2000bytes,初始狀態時C桶和E桶滿。
說明:爲方便計算,此處1Mbit/s按1*106bit/s計算。
l 假設第1個到達的報文是1500bytes時,檢查C桶發現令牌數大於數據包的長度,所以數據包被標爲綠色,C桶減少令牌1500bytes,還剩500bytes,E桶令牌數量保持不變。
l 假設1ms之後到達第2個報文1500bytes。在此間隔內,C桶新增令牌 = CIR * 1ms = 1000bit = 125bytes,加上C桶原來剩餘的令牌500bytes,此時C桶共有625bytes,檢查發現C桶內令牌數量不夠。檢查E桶發現有足夠令牌,因此報文標記爲***,E桶減少令牌1500bytes,剩餘500bytes,C桶剩餘625byte保持不變。
l 假設又過1ms後到達第3個報文1000bytes。在此間隔內,C桶新增令牌125bytes,加上C桶原來剩餘的令牌625bytes,此時C桶共有750bytes,檢查發現C桶內令牌數量不夠。檢查E桶發現令牌數量也不夠,因此報文被標記爲紅色,C桶、E桶令牌數不變。
l 假設又過20ms後到達第4個報文1500bytes。在此間隔內,C桶新增令牌 = CIR * 20ms = 20000bit = 2500bytes,加上C桶原來剩餘的令牌750bytes,C桶此時令牌數爲3250bytes。而CBS = 2000bytes,因此溢出的1250bytes添加到E桶,此時E桶有1750bytes。由於C桶中令牌數大於報文長度,報文標記爲綠色,C桶減少令牌1500bytes,剩餘500bytes,E桶不變。
報文處理過程彙總見下表。
包序號 |
時刻 (ms) |
包長 (bytes) |
與上次添加令牌的間隔 |
本輪增加令牌 |
令牌增加後各桶令牌 |
報文處理後各桶剩餘令牌 |
報文標記結果 |
||
C桶 |
E桶 |
C桶 |
E桶 |
||||||
2000 |
2000 |
2000 |
2000 |
- |
|||||
1 |
0 |
1500 |
0 |
0 |
2000 |
2000 |
500 |
2000 |
綠色 |
2 |
1 |
1500 |
1 |
125 |
625 |
2000 |
625 |
500 |
*** |
3 |
2 |
1000 |
1 |
125 |
750 |
500 |
750 |
500 |
紅色 |
4 |
22 |
1500 |
20 |
2500 |
2000 |
1750 |
500 |
1750 |
綠色 |
雙速雙桶
前面說到的自動出卡機,都只有一個口可以輸出通行卡。而這裏說到的高級自動出卡機,有兩個口可以出卡,一個口輸出的是通行卡,一個口輸出的是服務卡。當然,這裏也有兩個盒子用於盛放卡,分別是盒C盒和P盒。自動出卡機上的兩個口分別以各自固定的速率向兩個盒子中投放卡。(這個高級自動出卡機系統對應雙速雙桶模式,C盒和P盒對應雙速雙桶模式中的C桶和P桶。)
領取卡的規則和前面單速的情況有所不同。我們規定:
l 先領取服務卡。如果服務卡不夠,把卡放回P盒,紅燈亮,車輛禁止通行。
l 如果服務卡足夠但通行卡不夠,黃燈亮,服務卡可以取走,通行卡放回C盒。
l 如果服務卡和通行卡都足夠,綠燈亮,車輛可以通行,服務卡和通行卡都取走。
自動出卡機向P盒投放服務卡的速率和P盒的容量,就分別相當於雙速雙桶模式中的PIR和PBS。
PIR(Peak information rate):峯值信息速率,表示向P桶中投放令牌的速率,即P桶允許傳輸或轉發報文的峯值速率。PIR的值應大於CIR(存在服務卡足夠而通行卡不夠的情況)。
PBS(Peak Burst Size):峯值突發尺寸,表示P桶的容量,即P桶瞬間能夠通過的峯值突發流量。
按照國家高速公路交通安全法的規定,在高速公路上行駛的車輛,最高時速爲120km/h。前面領取的通行卡,保證車輛的時速爲100km/h。而服務卡的作用,則是允許車輛時速可以達到120km/h。自動出卡機向P盒投放服務卡的速率就是允許的最高車速,相當於PIR。
在雙速雙桶模式中,系統按照PIR速率向P桶中投放令牌,按照CIR速率向C桶中投放令牌。
l 如果P桶中可用令牌的總數量(Tp)小於PBS,則P桶中令牌數增加。
l 如果C桶中可用令牌的總數量(Tc)小於CBS,則C桶中令牌數增加。
對於到達的報文(報文大小爲B),
l 如果Tp < B,報文被標記爲紅色,且Tc和Tp都不減少。
l 如果Tc < B ≤ Tp,報文被標記爲***,且Tp減少B,Tc不減少。
l 如果B ≤ Tc,報文被標記爲綠色,且Tp和Tc都減少B。
假設設備端口的CIR設置爲1Mbit/s,PIR設置爲2Mbit/s,CBS爲2000 bytes,PBS爲3000 bytes,初始狀態時C桶和P桶滿。
說明:爲方便計算,此處1Mbit/s按1*106bit/s計算。
l 第1個到達的報文假設是1500bytes時,檢查發現報文長度不超過P桶也不超過C桶,所以數據包被標爲綠色,C桶和P桶都減少令牌1500bytes,C桶還剩500bytes,P桶還剩1500bytes。
l 假設1ms後到達第2個報文1800bytes。在此間隔內,P桶新增令牌 = PIR * 1ms = 2000bit = 250bytes,加上P桶原來剩餘的令牌1500bytes,此時P桶共有1750bytes,小於報文長度。C桶新增令牌 = CIR * 1ms = 1000bit = 125bytes,加上C桶原來剩餘的令牌500bytes,此時C桶共有625bytes。報文標記爲紅色,P桶、C桶令牌數不變。
l 假設又過1ms後到達第3個報文1000bytes。在此間隔內,P桶新增令牌250byte,加上P桶原來剩餘的令牌1750byte,此時P桶共有令牌2000bytes,大於報文長度。再檢查C桶,C桶新增令牌250bytes,加上C桶原來剩餘的令牌625byte,此時C桶共有750bytes,仍然小於報文長度。因此報文被標記爲***,P桶減少令牌1000bytes,剩餘1000bytes,C桶令牌不變。
l 假設又過20ms之後到達報文1500bytes。在此間隔內,P桶新增令牌 = PIR * 20ms = 40000bit = 5000bytes,超過P桶容量PBS,因此P桶令牌數 = PBS = 3000bytes,溢出的令牌丟棄。這樣P桶有2000bytes,大於報文長度。此時C桶增加令牌 = CIR * 20ms = 20000bit = 2500bytes,超過C桶容量CBS,因此C桶令牌數 = CBS = 2000byte,溢出的令牌丟棄。C桶此時令牌數2000 bytes,大於報文長度。報文被標記爲綠色,P桶減少令牌1500bytes,剩餘1500bytes;C桶減少令牌1500bytes,剩餘500bytes。
報文處理過程彙總見下表。
包序號 |
時刻 (ms) |
包長 (bytes) |
與上次添加令牌的間隔 |
本輪增加令牌 |
令牌增加後各桶令牌 |
報文處理後各桶剩餘令牌 |
報文標記結果 |
|||
C桶 |
P桶 |
C桶 |
P桶 |
C桶 |
P桶 |
|||||
|
2000 |
3000 |
2000 |
3000 |
- |
|||||
1 |
0 |
1500 |
0 |
0 |
0 |
2000 |
3000 |
500 |
1500 |
綠色 |
2 |
1 |
1800 |
1 |
125 |
250 |
625 |
1750 |
625 |
1750 |
紅色 |
3 |
2 |
1000 |
1 |
125 |
250 |
750 |
2000 |
750 |
1000 |
*** |
4 |
22 |
1500 |
20 |
2500 |
5000 |
2000 |
3000 |
500 |
1500 |
綠色 |
三種令牌桶模式的區別和應用場景
由前文描述可以看出,三種令牌桶模式之間既有區別也有演進關係,具體見下表。
|
單速單桶 |
單速雙桶 |
雙速雙桶 |
關鍵參數 |
CIR和CBS |
CIR、CBS和EBS |
CIR、CBS、PIR和PBS |
令牌投放 |
以CIR速率向C桶投放令牌。C桶滿時令牌溢出。 |
C桶滿時令牌投放到E桶。C桶和E桶都不滿時,只向C桶投放令牌。 |
以CIR速率向C桶投放令牌,以PIR速率向P桶中投放令牌。兩個桶相對獨立。桶中令牌滿時令牌溢出。 |
是否允許流量突發 |
不允許流量突發。報文的處理以C桶中是否有足夠令牌爲依據。 |
允許報文尺寸的突發。先使用C桶中的令牌,C桶中令牌數量不夠時,使用E桶中的令牌。 |
允許報文速率的突發。C桶和P桶中的令牌足夠時,兩個桶中的令牌都使用。C桶中令牌不夠時,只使用P桶中的令牌。 |
報文顏色標記結果 |
綠色或紅色 |
綠色、***或紅色 |
綠色、***或紅色 |
演進關係 |
單速雙桶模式中,如果EBS等於0,其效果和單速單桶是一樣的。 雙速雙桶模式中,如果PIR等於CIR,其效果和單速單桶是一樣的 |
基於上述三種令牌桶模式之間的區別,其功能和使用場景也有所不同,具體見下表。
令牌桶模式 |
功能 |
選用場景 |
單速單桶 |
限制帶寬 |
優先級較低的業務(如企業外網HTTP流量),對於超過額度的流量直接丟棄保證其他業務,不考慮突發。 |
單速雙桶 |
限制帶寬,還可以容許一部分流量突發,並且可以區分突發業務和正常業務 |
較爲重要的業務,容許有突發的業務(如企業郵件數據),對於突發流量有寬容。 |
雙速雙桶 |
限制帶寬,可以進行流量帶寬劃分,可以區別帶寬小於CIR還是在CIR ~PIR之間 |
重要業務,可以更好的監控流量的突發程度,對流量分析起到指導作用。 |
參數設置有講究
在令牌桶算法中,CIR的值越大,即令牌產生的速率越大,報文可以獲取的令牌就越多,流向網絡的流量也就越大。因此,CIR的值是控制流入網絡中流量多少的關鍵。CBS也是一個重要參數。CBS的值越大,C桶中可以積累令牌的數目也越多,允許通過的報文尺寸就越大。
由於設備進行的是逐包轉發,CBS的值不應該小於當前網絡上允許傳輸的報文的最大長度。
例如,在單速單桶模式下,假設要把流量限定在10Mbit/s,而CBS值太小(如設置成1000byte)。如果某個時間段內流量的報文,每個報文大小都大於1000byte,那麼這些報文全都被丟棄。這段時間內,沒有報文被轉發,報文的轉發速率爲0,導致網絡資源被浪費,業務也出現異常。
那是不是CBS的值越大就越好呢?顯然不是。CBS值太大,會失去限速的意義。例如,假設要把流量限定在10Mbit/s,CBS設置成7200Mbyte。某個時刻,令牌桶中的令牌已滿,如果接下來1小時內流量的報文,其報文長度不一,但共計7200Mbyte,這些報文都能獲得令牌並被轉發,那麼這段時間內的報文速率爲16Mbit/s(7200M * 8 / 3600)而不是10Mbit/s,即沒有實現限速。
同理,對於雙速雙桶模式,PIR和PBS的值也應設置在合理範圍內。
說明:目前華爲以太網交換機只支持單速單桶和雙速雙桶模式。
簡單來說,帶寬參數的設置取決於實際業務的限速需要。原則上,令牌桶容量需要大於等於網絡中可能出現的最大的報的長度和業務流量的正常突發量。對於華爲的以太網交換機,我們有總結的經驗性公式:
l 帶寬 ≤ 100Mbit/s時,令牌桶容量(Bytes) = 帶寬(kbit/s) * 1000(s)/8
l 帶寬 > 100Mbit/s時,令牌桶容量(Bytes) = 100000(kbit/s) * 1000 (s)/8
另外,華爲的以太網交換機,不同系列的單板支持的CAR粒度不同。在進行流量監管和限速時,
l 如果配置的CIR、PIR是最小粒度的整數倍,則按照配置的速率進行監管和限速。
l 如果配置的CIR、PIR不是最小粒度的整數倍,則按照最小粒度的整數倍進行流量監管和限速。
例如,設備上的監管和限速粒度爲64kbit/s,如果CIR值 ≤ 64kbit/s,按照64kbit/s處理;如果128kbit/s < CIR值 ≤ 192kbit/s,按照192kbit/s處理。
令牌桶原理應用之接口限速
令牌桶原理可以應用到設備的入方向和出方向。根據令牌桶原理在不同方向的應用,可以實現不同的功能,見下圖。
流量監管、流量整形、接口限速與令牌桶算法之間的關係,見下表。
應用方向 |
單速單桶 |
雙速雙桶 |
設備入方向 |
基於接口,實現接口限速 |
基於流,實現流量監管 |
設備出方向 |
基於接口,實現接口限速 |
基於隊列,實現流量整形 |
流量監管是一種通過對流量規格進行監督,以限制流量及網絡資源使用的流控策略。如果這種流控策略應用到設備接口的入方向,也可以實現入方向的接口限速。與基於接口實現的入方向的接口限速相比,這種方式引入了MQC(Modular QoS Command-Line Interface),因此應用更加靈活。
通常我們所說的限速是廣義上的接口限速,包括基於接口和基於MQC實現的入方向的接口限速。
http://www.javaranger.com/archives/1769
基於漏桶(Leaky bucket)與令牌桶(Token bucket)算法的流量控制
Eric 2015.11.06互聯網服務賴以生存的根本是流量, 產品和運營會經常通過各種方式來爲應用倒流,比如淘寶的雙十一等,如何讓系統在處理高併發的同時還是保證自身系統的穩定,通常在最短時間內提高併發的做法就是加機器,但是如果機器不夠怎麼辦?那就需要做業務降級或系統限流,流量控制中用的比較多的兩個算法就是漏桶和令牌桶.
漏桶算法(Leaky bucket)
漏桶算法強制一個常量的輸出速率而不管輸入數據流的突發性,當輸入空閒時,該算法不執行任何動作.就像用一個底部開了個洞的漏桶接水一樣,水進入到漏桶裏,桶裏的水通過下面的孔以固定的速率流出,當水流入速度過大會直接溢出,可以看出漏桶算法能強行限制數據的傳輸速率.如下圖所示:
令牌桶(Token bucket)
令牌桶算法的基本過程如下:
- 每秒會有 r 個令牌放入桶中,或者說,每過 1/r 秒桶中增加一個令牌
- 桶中最多存放 b 個令牌,如果桶滿了,新放入的令牌會被丟棄
- 當一個 n 字節的數據包到達時,消耗 n 個令牌,然後發送該數據包
- 如果桶中可用令牌小於 n,則該數據包將被緩存或丟棄
漏桶和令牌桶比較
“漏桶算法”能夠強行限制數據的傳輸速率,而“令牌桶算法”在能夠限制數據的平均傳輸數據外,還允許某種程度的突發傳輸。在“令牌桶算法”中,只要令牌桶中存在令牌,那麼就允許突發地傳輸數據直到達到用戶配置的上限,因此它適合於具有突發特性的流量。
RateLimiter
我們可以使用 Guava 的 RateLimiter 來實現基於令牌桶的流量控制。RateLimiter 令牌桶算法的單桶實現,RateLimiter 對簡單的令牌桶算法做了一些工程上的優化,具體的實現是 SmoothBursty。需要注意的是,RateLimiter 的另一個實現 SmoothWarmingUp,就不是令牌桶了,而是漏桶算法。
SmoothBursty 有一個可以放 N 個時間窗口產生的令牌的桶,系統空閒的時候令牌就一直攢着,最好情況下可以扛 N 倍於限流值的高峯而不影響後續請求,就像三峽大壩一樣能扛千年一遇的洪水.
(轉載本站文章請註明作者和出處 JavaRanger – javaranger.com ,請勿用於任何商業用途)
本文鏈接: http://www.javaranger.com/archives/1769
http://www.cnblogs.com/exceptioneye/p/4783904.html
服務接口API限流 Rate Limit
一、場景描述
很多做服務接口的人或多或少的遇到這樣的場景,由於業務應用系統的負載能力有限,爲了防止非預期的請求對系統壓力過大而拖垮業務應用系統。
也就是面對大流量時,如何進行流量控制?
服務接口的流量控制策略:分流、降級、限流等。本文討論下限流策略,雖然降低了服務接口的訪問頻率和併發量,卻換取服務接口和業務應用系統的高可用。
實際場景中常用的限流策略:
- Nginx前端限流
按照一定的規則如帳號、IP、系統調用邏輯等在Nginx層面做限流
- 業務應用系統限流
1、客戶端限流
2、服務端限流
- 數據庫限流
紅線區,力保數據庫
二、常用的限流算法
常用的限流算法由:樓桶算法和令牌桶算法。本文不具體的詳細說明兩種算法的原理,原理會在接下來的文章中做說明。
1、漏桶算法
漏桶(Leaky Bucket)算法思路很簡單,水(請求)先進入到漏桶裏,漏桶以一定的速度出水(接口有響應速率),當水流入速度過大會直接溢出(訪問頻率超過接口響應速率),然後就拒絕請求,可以看出漏桶算法能強行限制數據的傳輸速率.示意圖如下:
可見這裏有兩個變量,一個是桶的大小,支持流量突發增多時可以存多少的水(burst),另一個是水桶漏洞的大小(rate)。
因爲漏桶的漏出速率是固定的參數,所以,即使網絡中不存在資源衝突(沒有發生擁塞),漏桶算法也不能使流突發(burst)到端口速率.因此,漏桶算法對於存在突發特性的流量來說缺乏效率.
2、令牌桶算法
令牌桶算法(Token Bucket)和 Leaky Bucket 效果一樣但方向相反的算法,更加容易理解.隨着時間流逝,系統會按恆定1/QPS時間間隔(如果QPS=100,則間隔是10ms)往桶裏加入Token(想象和漏洞漏水相反,有個水龍頭在不斷的加水),如果桶已經滿了就不再加了.新請求來臨時,會各自拿走一個Token,如果沒有Token可拿了就阻塞或者拒絕服務.
令牌桶的另外一個好處是可以方便的改變速度. 一旦需要提高速率,則按需提高放入桶中的令牌的速率. 一般會定時(比如100毫秒)往桶中增加一定數量的令牌, 有些變種算法則實時的計算應該增加的令牌的數量.
三、基於Redis功能的實現
簡陋的設計思路:假設一個用戶(用IP判斷)每分鐘訪問某一個服務接口的次數不能超過10次,那麼我們可以在Redis中創建一個鍵,並此時我們就設置鍵的過期時間爲60秒,每一個用戶對此服務接口的訪問就把鍵值加1,在60秒內當鍵值增加到10的時候,就禁止訪問服務接口。在某種場景中添加訪問時間間隔還是很有必要的。
1)使用Redis的incr命令,將計數器作爲Lua腳本
1 local current 2 current = redis.call("incr",KEYS[1]) 3 if tonumber(current) == 1 then 4 redis.call("expire",KEYS[1],1) 5 end
Lua腳本在Redis中運行,保證了incr和expire兩個操作的原子性。
2)使用Reids的列表結構代替incr命令
1 FUNCTION LIMIT_API_CALL(ip) 2 current = LLEN(ip) 3 IF current > 10 THEN 4 ERROR "too many requests per second" 5 ELSE 6 IF EXISTS(ip) == FALSE 7 MULTI 8 RPUSH(ip,ip) 9 EXPIRE(ip,1) 10 EXEC 11 ELSE 12 RPUSHX(ip,ip) 13 END 14 PERFORM_API_CALL() 15 END
Rate Limit使用Redis的列表作爲容器,LLEN用於對訪問次數的檢查,一個事物中包含了RPUSH和EXPIRE兩個命令,用於在第一次執行計數是創建列表並設置過期時間,
RPUSHX在後續的計數操作中進行增加操作。
四、基於令牌桶算法的實現
令牌桶算法可以很好的支撐突然額流量的變化即滿令牌桶數的峯值。
1 import java.io.BufferedWriter; 2 import java.io.FileOutputStream; 3 import java.io.IOException; 4 import java.io.OutputStreamWriter; 5 import java.util.Random; 6 import java.util.concurrent.ArrayBlockingQueue; 7 import java.util.concurrent.Executors; 8 import java.util.concurrent.ScheduledExecutorService; 9 import java.util.concurrent.TimeUnit; 10 import java.util.concurrent.locks.ReentrantLock; 11 12 import com.google.common.base.Preconditions; 13 import com.netease.datastream.util.framework.LifeCycle; 14 15 20 public class TokenBucket implements LifeCycle { 21 22 // 默認桶大小個數 即最大瞬間流量是64M 23 private static final int DEFAULT_BUCKET_SIZE = 1024 * 1024 * 64; 24 25 // 一個桶的單位是1字節 26 private int everyTokenSize = 1; 27 28 // 瞬間最大流量 29 private int maxFlowRate; 30 31 // 平均流量 32 private int avgFlowRate; 33 34 // 隊列來緩存桶數量:最大的流量峯值就是 = everyTokenSize*DEFAULT_BUCKET_SIZE 64M = 1 * 1024 * 1024 * 64 35 private ArrayBlockingQueue<Byte> tokenQueue = new ArrayBlockingQueue<Byte>(DEFAULT_BUCKET_SIZE); 36 37 private ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); 38 39 private volatile boolean isStart = false; 40 41 private ReentrantLock lock = new ReentrantLock(true); 42 43 private static final byte A_CHAR = 'a'; 44 45 public TokenBucket() { 46 } 47 48 public TokenBucket(int maxFlowRate, int avgFlowRate) { 49 this.maxFlowRate = maxFlowRate; 50 this.avgFlowRate = avgFlowRate; 51 } 52 53 public TokenBucket(int everyTokenSize, int maxFlowRate, int avgFlowRate) { 54 this.everyTokenSize = everyTokenSize; 55 this.maxFlowRate = maxFlowRate; 56 this.avgFlowRate = avgFlowRate; 57 } 58 59 public void addTokens(Integer tokenNum) { 60 61 // 若是桶已經滿了,就不再家如新的令牌 62 for (int i = 0; i < tokenNum; i++) { 63 tokenQueue.offer(Byte.valueOf(A_CHAR)); 64 } 65 } 66 67 public TokenBucket build() { 68 69 start(); 70 return this; 71 } 72 73 /** 74 * 獲取足夠的令牌個數 75 * 76 * @return 77 */ 78 public boolean getTokens(byte[] dataSize) { 79 80 Preconditions.checkNotNull(dataSize); 81 Preconditions.checkArgument(isStart, "please invoke start method first !"); 82 83 int needTokenNum = dataSize.length / everyTokenSize + 1;// 傳輸內容大小對應的桶個數 84 85 final ReentrantLock lock = this.lock; 86 lock.lock(); 87 try { 88 boolean result = needTokenNum <= tokenQueue.size(); // 是否存在足夠的桶數量 89 if (!result) { 90 return false; 91 } 92 93 int tokenCount = 0; 94 for (int i = 0; i < needTokenNum; i++) { 95 Byte poll = tokenQueue.poll(); 96 if (poll != null) { 97 tokenCount++; 98 } 99 } 100 101 return tokenCount == needTokenNum; 102 } finally { 103 lock.unlock(); 104 } 105 } 106 107 @Override 108 public void start() { 109 110 // 初始化桶隊列大小 111 if (maxFlowRate != 0) { 112 tokenQueue = new ArrayBlockingQueue<Byte>(maxFlowRate); 113 } 114 115 // 初始化令牌生產者 116 TokenProducer tokenProducer = new TokenProducer(avgFlowRate, this); 117 scheduledExecutorService.scheduleAtFixedRate(tokenProducer, 0, 1, TimeUnit.SECONDS); 118 isStart = true; 119 120 } 121 122 @Override 123 public void stop() { 124 isStart = false; 125 scheduledExecutorService.shutdown(); 126 } 127 128 @Override 129 public boolean isStarted() { 130 return isStart; 131 } 132 133 class TokenProducer implements Runnable { 134 135 private int avgFlowRate; 136 private TokenBucket tokenBucket; 137 138 public TokenProducer(int avgFlowRate, TokenBucket tokenBucket) { 139 this.avgFlowRate = avgFlowRate; 140 this.tokenBucket = tokenBucket; 141 } 142 143 @Override 144 public void run() { 145 tokenBucket.addTokens(avgFlowRate); 146 } 147 } 148 149 public static TokenBucket newBuilder() { 150 return new TokenBucket(); 151 } 152 153 public TokenBucket everyTokenSize(int everyTokenSize) { 154 this.everyTokenSize = everyTokenSize; 155 return this; 156 } 157 158 public TokenBucket maxFlowRate(int maxFlowRate) { 159 this.maxFlowRate = maxFlowRate; 160 return this; 161 } 162 163 public TokenBucket avgFlowRate(int avgFlowRate) { 164 this.avgFlowRate = avgFlowRate; 165 return this; 166 } 167 168 private String stringCopy(String data, int copyNum) { 169 170 StringBuilder sbuilder = new StringBuilder(data.length() * copyNum); 171 172 for (int i = 0; i < copyNum; i++) { 173 sbuilder.append(data); 174 } 175 176 return sbuilder.toString(); 177 178 } 179 180 public static void main(String[] args) throws IOException, InterruptedException { 181 182 tokenTest(); 183 } 184 185 private static void arrayTest() { 186 ArrayBlockingQueue<Integer> tokenQueue = new ArrayBlockingQueue<Integer>(10); 187 tokenQueue.offer(1); 188 tokenQueue.offer(1); 189 tokenQueue.offer(1); 190 System.out.println(tokenQueue.size()); 191 System.out.println(tokenQueue.remainingCapacity()); 192 } 193 194 private static void tokenTest() throws InterruptedException, IOException { 195 TokenBucket tokenBucket = TokenBucket.newBuilder().avgFlowRate(512).maxFlowRate(1024).build(); 196 197 BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("/tmp/ds_test"))); 198 String data = "xxxx";// 四個字節 199 for (int i = 1; i <= 1000; i++) { 200 201 Random random = new Random(); 202 int i1 = random.nextInt(100); 203 boolean tokens = tokenBucket.getTokens(tokenBucket.stringCopy(data, i1).getBytes()); 204 TimeUnit.MILLISECONDS.sleep(100); 205 if (tokens) { 206 bufferedWriter.write("token pass --- index:" + i1); 207 System.out.println("token pass --- index:" + i1); 208 } else { 209 bufferedWriter.write("token rejuect --- index" + i1); 210 System.out.println("token rejuect --- index" + i1); 211 } 212 213 bufferedWriter.newLine(); 214 bufferedWriter.flush(); 215 } 216 217 bufferedWriter.close(); 218 } 219 220 }
作者:三石雨
出處:http://www.cnblogs.com/exceptioneye
參考:
http://xiaobaoqiu.github.io/blog/2015/07/02/ratelimiter/
http://redisdoc.com/string/incr.html
http://www.cnblogs.com/zhengyun_ustc/archive/2012/11/17/topic1.html