在開發高併發系統時有三把利器用來保護系統:緩存、降級和限流。今天咱們要聊的就是限流(Rate Limit),限流的目的很簡單,就是爲了保護系統不被瞬時大流量沖垮,html
須要用到限流,特別是相似秒殺這種瞬時流量很是大但實際成單率低的業務場景。java
目前比較經常使用的限流算法有三種git
計數器固定窗口算法github
計數器滑動窗口算法算法
漏桶算法緩存
令牌桶算法併發
計數器固定窗口算法是最簡單的限流算法,實現方式也比較簡單。就是經過維護一個單位時間內的計數值,每當一個請求經過時,就將計數值加1,當計數值超過預先設定的閾值時,就拒絕單位時間內的其餘請求。若是單位時間已經結束,則將計數器清零,開啓下一輪的計數。高併發
可是這種實現會有一個問題,舉個例子:post
假設咱們設定1秒內容許經過的請求閾值是200,若是有用戶在時間窗口的最後幾毫秒發送了200個請求,緊接着又在下一個時間窗口開始時發送了200個請求,那麼這個用戶其實在一秒內成功請求了400次,顯然超過了閾值但並不會被限流。其實這就是臨界值問題,那麼臨界值問題要怎麼解決呢?url
計數器滑動窗口法就是爲了解決上述固定窗口計數存在的問題而誕生,學過TCP協議的同窗應該對滑動窗口不陌生,其實仍是不太同樣的,下文咱們要說的滑動窗口是基於時間來劃分窗口的。而TCP的滑動窗口指的是可以接受的字節數,而且大小是可變的(擁塞控制)
滑動窗口是怎麼作的?
前面說了固定窗口存在臨界值問題,要解決這種臨界值問題,顯然只用一個窗口是解決不了問題的。假設咱們仍然設定1秒內容許經過的請求是200個,可是在這裏咱們須要把1秒的時間分紅多格,假設分紅5格(格數越多,流量過渡越平滑),每格窗口的時間大小是200毫秒,每過200毫秒,就將窗口向前移動一格。爲了便於理解,能夠看下圖
圖中將窗口劃爲5份,每一個小窗口中的數字表示在這個窗口中請求數,因此經過觀察上圖,可知在當前時間快(200毫秒)容許經過的請求數應該是20而不是200(只要超過20就會被限流),由於咱們最終統計請求數時是須要把當前窗口的值進行累加,進而獲得當前請求數來判斷是否是須要進行限流。
那麼滑動窗口限流法是完美的嗎?
細心觀察的咱們應該能立刻發現問題,滑動窗口限流法其實就是計數器固定窗口算法的一個變種。流量的過渡是否平滑依賴於咱們設置的窗口格數也就是統計時間間隔,格數越多,統計越精確,可是具體要分多少格咱們也說不上來呀...
上面所介紹的兩種算法都不能很是平滑的過渡,下面就是漏桶算法登場了
什麼是漏桶算法?
漏桶算法以一個常量限制了出口流量速率,所以漏桶算法能夠平滑突發的流量。其中漏桶做爲流量容器咱們能夠看作一個FIFO的隊列,當入口流量速率大於出口流量速率時,由於流量容器是有限的,當超出流量容器大小時,超出的流量會被丟棄。
下圖比較形象的說明了漏桶算法的原理,其中水龍頭是入口流量,漏桶是流量容器,勻速流出的水是出口流量。
漏桶算法的特色
漏桶具備固定容量,出口流量速率是固定常量(流出請求)
入口流量能夠以任意速率流入到漏桶中(流入請求)
若是入口流量超出了桶的容量,則流入流量會溢出(新請求被拒絕)
代碼實現 -- [LeakyBucketRateLimit.java](https://github.com/WangJunnan/learn/blob/master/algorithm/src/main/java/com/walm/learn/algorithm/ratelimit/LeakyBucketRateLimit.java)
不過由於漏桶算法限制了流出速率是一個固定常量值,因此漏桶算法不支持出現突發流出流量。可是在實際狀況下,流量每每是突發的。
令牌桶算法是漏桶算法的改進版,能夠支持突發流量。不過與漏桶算法不一樣的是,令牌桶算法的漏桶中存放的是令牌而不是流量。
那麼令牌桶算法是怎麼突發流量的呢?
最開始,令牌桶是空的,咱們以恆定速率往令牌桶裏加入令牌,令牌桶被裝滿時,多餘的令牌會被丟棄。當請求到來時,會先嚐試從令牌桶獲取令牌(至關於從令牌桶移除一個令牌),獲取成功則請求被放行,獲取失敗則阻塞活拒絕請求。
令牌桶算法的特色
最多能夠存發b個令牌。若是令牌到達時令牌桶已經滿了,那麼這個令牌會被丟棄
請求到來時,若是令牌桶中少於n個令牌,那麼不會刪除令牌。該請求會被限流(阻塞活拒絕)
算法容許最大b(令牌桶大小)個請求的突發
令牌桶算法限制的是平均流量,所以其容許突發流量(只要令牌桶中有令牌,就不會被限流)
至此,基本把以上4種限流算法的原理都解釋清楚了。每種限流算法都有其固定特色,及各自適用的場景,其中計數器算法是其中最簡單的,至關於滑動窗口算法的簡化版,令牌桶算法相比漏桶算法對資源的利用率更高(容許突發流量)
參考:http://www.javashuo.com/article/p-cibybhjl-mt.html