限流的目的是經過對併發訪問/請求進行限速或者一個時間窗口內的的請求進行限速來保護系統,一旦併發訪問/請求達到限制速率或者超過其承受範圍時候則能夠拒絕服務、排隊或引流。html
目前經常使用的限流算法有兩個:漏桶算法和令牌桶算法。java
http://en.wikipedia.org/wiki/Leaky_bucket算法
根據wiki上的介紹,Leaky Bucket實際上有兩種不一樣的含義。api
1)as a meter(做爲計量工具)緩存
2)as a queue(做爲調度隊列)網絡
Leaky Bucket 做爲計量工具,以下所示:併發
如圖,桶自己具備一個恆定的速率往下漏水,而上方時快時慢地會有水進入桶中。當桶還未滿時,上方的水能夠加入。一旦水滿,上方的水就沒法加入了。桶滿正是算法中的一個的關鍵觸發條件(即流量異常判斷成立的條件)。而此條件下如何處理上方漏下的水,則有了下面兩種常見的方式。app
在桶滿水以後,常見的兩種處理方式爲:ssh
1)暫時攔截住上方水的向下流動,等待桶中的一部分水漏走後,再放行上方水。工具
2)溢出的上方水直接拋棄。
將水看做網絡通訊中數據包的抽象,則方式1起到的效果稱爲Traffic Shaping,方式2起到的效果稱爲Traffic Policing。
The following diagram illustrates the key difference. Traffic policing propagates bursts. When the traffic rate reaches the configured maximum rate, excess traffic is dropped (or remarked). The result is an output rate that appears as a saw-tooth with crests and troughs. In contrast to policing, traffic shaping retains excess packets in a queue and then schedules the excess for later transmission over increments of time. The result of traffic shaping is a smoothed packet output rate.
參考:https://www.cisco.com/c/en/us/support/docs/quality-of-service-qos/qos-policing/19645-policevsshape.html
對於不少應用場景來講,除了要求可以限制數據的平均傳輸速率外,還要求容許某種程度的突發傳輸。這時候漏桶算法可能就不合適了,令牌桶算法更爲適合。
https://en.wikipedia.org/wiki/Token_bucket
令牌桶算法最初來源於計算機網絡。在網絡傳輸數據時,爲了防止網絡擁塞,需限制流出網絡的流量,使流量以比較均勻的速度向外發送。令牌桶算法就實現了這個功能,可控制發送到網絡上數據的數目,並容許突發數據的發送。
令牌桶算法是網絡流量整形(Traffic Shaping)和速率限制(Rate Limiting)中最常使用的一種算法。典型狀況下,令牌桶算法用來控制發送到網絡上的數據的數目,並容許突發數據的發送。
大小固定的令牌桶可自行以恆定的速率源源不斷地產生令牌。若是令牌不被消耗,或者被消耗的速度小於產生的速度,令牌就會不斷地增多,直到把桶填滿。後面再產生的令牌就會從桶中溢出。最後桶中能夠保存的最大令牌數永遠不會超過桶的大小。
傳送到令牌桶的數據包須要消耗令牌。不一樣大小的數據包,消耗的令牌數量不同。
算法描述:
漏桶的出水速度是恆定的,那麼意味着若是瞬時大流量的話,將有大部分請求被丟棄掉(也就是所謂的溢出)。
生成令牌的速度是恆定的,而請求去拿令牌是沒有速度限制的。這意味,面對瞬時大流量,該算法能夠在短期內請求拿到大量令牌,並且拿令牌的過程並非消耗很大的事情。
Guava RateLimiter提供了令牌桶算法實現:平滑突發限流(SmoothBursty)和平滑預熱限流(SmoothWarmingUp)實現。
//RateLimiter.create(5) 表示桶容量爲5且每秒新增5個令牌,即每隔200毫秒新增一個令牌; RateLimiter limiter = RateLimiter.create(5); System.out.println(limiter.acquire()); System.out.println(limiter.acquire()); System.out.println(limiter.acquire()); System.out.println(limiter.acquire()); System.out.println(limiter.acquire()); System.out.println(limiter.acquire());
輸入以下,
0.0 0.191546 0.19138 0.197585 0.194843 0.195557
突發的請求速率以下,
// guava Stopwatch Stopwatch stopwatch = Stopwatch.createUnstarted(); //RateLimiter.create(5) 表示桶容量爲5且每秒新增5個令牌,即每隔200毫秒新增一個令牌; RateLimiter limiter = RateLimiter.create(5); System.out.println(limiter.acquire(10)); // 0.0,獲取令牌成功 System.out.println("獲取令牌start..."); stopwatch.start(); System.out.println(limiter.acquire(1)); long time = stopwatch.elapsed(TimeUnit.MILLISECONDS); System.out.println("獲取令牌花費的時間=" + time); System.out.println(limiter.acquire(1));
輸出以下,
獲取令牌start... 1.996403 獲取令牌花費的時間=2002 0.193232
第一秒突發了10個請求,令牌桶算法也容許了這種突發(容許消費將來的令牌),但接下來的limiter.acquire(1)將等待差很少2秒桶中才能有令牌,且接下來的請求也整形爲固定速率了。
平滑預熱限流方式建立以下,
RateLimiter limiter = RateLimiter.create(5, 1000, TimeUnit.MILLISECONDS); System.out.println(limiter.acquire()); System.out.println(limiter.acquire()); System.out.println(limiter.acquire()); System.out.println(limiter.acquire()); System.out.println(limiter.acquire()); Thread.sleep(1000L); System.out.println("sleep end..."); System.out.println(limiter.acquire()); System.out.println(limiter.acquire()); System.out.println(limiter.acquire()); System.out.println(limiter.acquire()); System.out.println(limiter.acquire());
輸出以下,
0.0 0.51702 0.355476 0.220346 0.197056 sleep end... 0.0 0.365465 0.221425 0.199815 0.199381
能夠看到剛開始獲取令牌所花費的時候比較大,隨着時間的推移,所花費的時間愈來愈少,趨於穩定,說明速率趨於穩定。這就是所謂的預熱。
======END======