它的主要目的是控制數據注入到網絡的速率,平滑網絡上的突發流量。漏桶算法提供了一種機制,經過它,突發流量能夠被整形以便爲網絡提供一個穩定的流量。 漏桶能夠看做是一個帶有常量服務時間的單服務器隊列,若是漏桶(包緩存)溢出,那麼數據包會被丟棄。 簡單的理解爲:漏桶算法思路很簡單,水(數據或者請求)先進入到漏桶裏,漏桶以必定的速度出水,當水流入速度過大會直接溢出,能夠看出漏桶算法能強行限制數據的傳輸速率。php
在桶滿水以後,常見的兩種處理方式爲:html
1)暫時攔截住上方水的向下流動,等待桶中的一部分水漏走後,再放行上方水。java
2)溢出的上方水直接拋棄。python
將水看做網絡通訊中數據包的抽象,則nginx
方式1起到的效果稱爲Traffic Shaping(流量整形),git
方式2起到的效果稱爲Traffic Policing(流量策略)。github
因而可知,Traffic Shaping 的核心理念是「等待」,Traffic Policing 的核心理念是「丟棄」。它們是兩種常見的流速控制方法。redis
在某些狀況下,漏桶算法不可以有效地使用網絡資源。由於漏桶的漏出速率是固定的參數(恆定的速率往下漏水),因此,即便網絡中不存在資源衝突(沒有發生擁塞),漏桶算法也不能使某一個單獨的流突發到端口速率。所以,漏桶算法對於存在突發特性的流量來講缺少效率。而令牌桶算法則可以知足這些具備突發特性的流量。一般,漏桶算法與令牌桶算法能夠結合起來爲網絡流量提供更大的控制。算法
令牌桶算法的原理是系統會以一個恆定的速度往桶裏放入令牌,而若是請求須要被處理,則須要先從桶裏獲取一個令牌,當桶裏沒有令牌可取時,則拒絕服務。 令牌桶的另一個好處是能夠方便的改變速度。 一旦須要提升速率,則按需提升放入桶中的令牌的速率。 通常會定時(好比100毫秒)往桶中增長必定數量的令牌, 有些變種算法則實時的計算應該增長的令牌的數量, 好比華爲的專利"採用令牌漏桶進行報文限流的方法"(CN 1536815 A),提供了一種動態計算可用令牌數的方法, 相比其它定時增長令牌的方法, 它只在收到一個報文後,計算該報文與前一報文到來的時間間隔內向令牌漏桶內注入的令牌數, 並計算判斷桶內的令牌數是否知足傳送該報文的要求。後端
從整個架構的穩定性角度看,通常 SOA 架構的每一個接口在有限資源的狀況下,所能提供的單位時間服務能力是有限的。假如超過服務能力,通常會形成整個接口服務停頓,或者應用 Crash(宕機),或者帶來連鎖反應,將延遲傳遞給服務調用方形成整個系統的服務能力喪失。有必要在服務能力超限的狀況下快速失敗(Fail Fast)。另外,根據排隊論,因爲 API 接口服務具備延遲隨着請求量提高迅速提高的特色,爲了保證 SLA 的低延遲,須要控制單位時間的請求量。這也是 Little’s law 所說的。
還有,公開 API 接口服務,速率限制(Rate limiting)應該是一個必備的功能,不然公開的接口不知道哪一天就會被服務調用方有意無心的打垮。因此,提供資源可以支撐的服務,將過載請求快速拋棄對整個系統架構的穩定性很是重要。這就要求在應用層實現 Rate limiting 限制。
Nginx 模塊
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
server {
location /search/ {
limit_req zone=one burst=5;
}
詳細參見: ngx_http_limit_req_module
詳細參見: Haproxy Rate limit 模塊
這些策略可用於速率限制請求不一樣的網站中,後端或 API 調用等場景。
這個在 Redis 官方文檔有很是詳細的實現。通常適用於全部類型的應用,好比 PHP、Python 等等。Redis 的實現方式能夠支持分佈式服務的訪問頻率的集中控制。Redis 的頻率限制實現方式還適用於在應用中沒法狀態保存狀態的場景。
Google開源工具包Guava提供了限流工具類RateLimiter,該類基於令牌桶算法來完成限流,很是易於使用。RateLimiter常常用於限制對一些物理資源或者邏輯資源的訪問速率.它支持兩種獲取許可令牌(permits)接口,一種是若是拿不到馬上返回false,一種會阻塞等待一段時間看能不能拿到.
Eg:阻塞等待
//每秒建立五個令牌,即沒200ms建立一個令牌 RateLimiter rateLimiter = RateLimiter.create(5.0); public static void main(String[] args) throws Exception { RetaLimiterLearn rll = new RetaLimiterLearn(); rll.oneTest(); } public void oneTest() { for (int i = 0; i < 5; i++) { //消耗一個令牌,線程會阻塞。 System.out.println(rateLimiter.acquire()); } }
若是當前桶中有足夠令牌則成功(返回值爲0),若是桶中沒有令牌則暫停一段時間,好比發令牌間隔是200毫秒,則等待200毫秒後再去消費令牌,這種實現將突發請求速率平均爲了固定請求速率。
再看一個例子:
public void twoTest() { //一次獲取5個令牌 System.out.println(rateLimiter.acquire(5)); for (int i = 0; i < 5; i++) { System.out.println(rateLimiter.acquire()); } }
令牌桶算法容許必定程度的突發,因此能夠一次性消費5個令牌,但接下來的limiter.acquire(1)將等待差很少1秒桶中才能有令牌,且接下來的請求也整形爲固定速率了。
public void threeTest() throws Exception{ //建立了一個桶容量爲2且每秒新增2個令牌 RateLimiter limiter = RateLimiter.create(2); //消費一個令牌,此時令牌桶能夠知足(返回值爲0) System.out.println(limiter.acquire()); //線程暫停2秒 Thread.sleep(2000L); for (int i = 0; i < 5; i++) { System.out.println(limiter.acquire()); } }
咱們發現到第四個acquire時就須要等待500毫秒了。此處能夠看到咱們設置的桶容量爲2(即容許的突發量),這是由於平滑突發限流(SmoothBursty)中有一個參數:最大突發秒數(maxBurstSeconds)默認值是1s,突發量/桶容量=速率*maxBurstSeconds,因此本示例桶容量/突發量爲2,例子中前兩個是消費了以前積攢的突發量,而第三個開始就是正常計算的了。令牌桶算法容許將一段時間內沒有消費的令牌暫存到令牌桶中,留待將來使用,並容許將來請求的這種突發。
SmoothBursty經過平均速率和最後一次新增令牌的時間計算出下次新增令牌的時間的,另外須要一個桶暫存一段時間內沒有使用的令牌(便可以突發的令牌數)。
RateLimiter還提供了tryAcquire方法來進行無阻塞或可超時的令牌消費。由於SmoothBursty容許必定程度的突發,會有人擔憂若是容許這種突發,假設忽然間來了很大的流量,那麼系統極可能扛不住這種突發。所以須要一種平滑速率的限流工具,從而系統冷啓動後慢慢的趨於平均固定速率(即剛開始速率小一些,而後慢慢趨於咱們設置的固定速率)。Guava也提供了平滑預熱限流(SmoothWarmingUp)來實現這種需求,其能夠認爲是漏桶算法,可是在某些特殊場景又不太同樣。
SmoothWarmingUp建立方式:
RateLimiter.create(double permitsPerSecond, long warmupPeriod, TimeUnit unit)
permitsPerSecond表示每秒新增的令牌數,warmupPeriod表示在從冷啓動速率過渡到平均速率的時間間隔。
public void fourTest() throws InterruptedException { /** * permitsPerSecond表示每秒新增的令牌數 * warmupPeriod表示在從冷啓動速率過渡到平均速率的時間間隔。 * unit 時間單位 毫秒 */ RateLimiter limiter = RateLimiter.create(5, 1000, TimeUnit.MILLISECONDS); for(int i = 1; i < 5;i++) { System.out.println(limiter.acquire()); } //線程暫停1秒 Thread.sleep(1000L); for(int i = 1; i < 5;i++) { System.out.println(limiter.acquire()); } }
tryAcquire()的用法:
If(limiter.tryAcquire()){ //未請求到limiter則當即返回false
doSomething();
}else{
doSomethingElse();
}
<dubbo:protocol name="dubbo" port="20881"threadpool="limited" threads="200"/>