背景 最近瞭解到很多朋友對限流、熔斷、降級、隔離、超時重試的概念和應用場景理解的不是很到位,所以想用五篇的篇幅稍微系統的介紹一下。 本篇是第一篇,是限流做詳解,如果反饋好的話,我會繼續寫下麵四篇。不好的話就算了,算我理解不夠,再自己總結總結。 限流的概念 有朋友問我限流和熔斷有什麼區別,我的理解很簡 ...
背景
最近瞭解到很多朋友對限流、熔斷、降級、隔離、超時重試的概念和應用場景理解的不是很到位,所以想用五篇的篇幅稍微系統的介紹一下。
本篇是第一篇,是限流做詳解,如果反饋好的話,我會繼續寫下麵四篇。不好的話就算了,算我理解不夠,再自己總結總結。
限流的概念
有朋友問我限流和熔斷有什麼區別,我的理解很簡單。限流作用是防禦上游流量超過處理能力的手段,熔斷作用是容錯下游的快速失敗手段。
舉個生活中的限流例子:
小A最近打算找個女朋友,他拜托了很多朋友幫自己介紹,朋友們也很給力,很多姑娘都願意和小A聊一聊。小A發現時間忙不開了,他就制定了一個計劃,一天見2個。這就是限流。
舉個生活中的熔斷例子:
小A在見這些姑娘的時候,如果有的姑娘不守時,超過約定時間半小時還沒有出現,那小A就會離開。不然會耽誤見下一位姑娘,這是一種熔斷手段。另外,如果有的姑娘特別能說,聊天超過了3小時,小A也會打斷姑娘,把姑娘先送走,不然也會耽誤見下一位姑娘。這也是需要的熔斷措施。
限流的原理
不管任何編程語言的實現,目前主流的底層就是基於令牌桶演算法和漏斗演算法。這兩種演算法達到的效果有所不同。
令牌桶演算法
令牌桶演算法是先有個固定容量的桶,一個任務會以固定的速率往桶里放token,請求來了會去取token。如果桶滿了,token就溢出了。多出來的token就不要了。如果請求太快,token生產速度跟不上消費速率,桶空了,有的請求取不到token,這時候就會直接返回錯誤而不繼續處理。
舉個例子:
比如小A最後找到了心儀的女朋友小C。他倆相處融洽,一起包餃子吃。小A負責擀皮,小C負責包。小A會把擀好的皮放到一塊案板上。這個案板可以放20張皮。如果皮擀多了,就放不下,這時候小A就會停下來等。如果皮擀的慢,小C沒的包,也就只能停下來。這裡的皮就相當於是token,包餃子就相當於是處理業務的請求。用圖表示如下:
漏斗演算法
漏斗演算法也是先有個固定容量的桶,請求來了先經過桶,從桶里出去的速率是一定的。如果請求量讓桶滿了,多出來的請求就不處理了。如果桶是空的,新來的請求就能馬上處理。
事實上,各種MQ比如kafka就是典型漏斗演算法。broker就是這個固定容量的桶,生產者會不斷的將數據寫到broker里,消費者是採用的拉取模式,總是以固定的速率來消費。
令牌桶演算法和漏洞演算法的比較
限流的實現
基礎實現
在Java中業界用的比較多的是Google出品的Guava RateLimiter和另外的一款resilience4j-ratelimiter來實現限流。原理差不多。
下麵以RateLimiter為例進行講解。要實現一個限流總共需要用到RateLimiter的兩個方法:
1>RateLimiter.create() 靜態方法創建對象,初始化桶容量
2>acquire()或者tryAcquire() 獲取請求token,兩者使用一個即可。acquire方法是阻塞式的,用來實現漏斗演算法;tryAcquire是非阻塞式的,用來實現令牌桶演算法。
阻塞式是如果到達指定條件前一直不返回結果,通過下麵的源碼可看到內部實際上是用sleep來實現的阻塞。因為所有的請求獲取許可權時都會sleep固定的時間才返回,就達到了勻速的目的。
非阻塞是立即返回是否獲取到許可權(token)。這時候請求如果獲取許可權成功就處理請求,獲取許可權失敗就直接返回一個自定義的快速失敗處理方式。平時請求速率小於token產生速率,桶漸漸滿了。一旦有突發流量,因為桶里有存量token,也可以直接獲取到許可權,就是為什麼令牌桶演算法可以應對突發流量的原理。
高階實現
上面實現里講的是工具組件,如果只使用工具組件有個問題。限流實際上需要定期進行容量評估,是一個動態的過程,如果只使用工具組件就需要每次修改代碼。當然也可以將每個值寫到一個統一配置里,比如zookeeper來進行管理。
如果規模大的情況下更好的一個解決方法是使用專門的平臺。這個平臺可以支撐更多維度的配置,比如集群維度的限流。集群維度和單機維度的區別是如果設置了一個總的閾值,系統可以根據機器資源情況自動計算出每台機器的限流情況。
在業界,阿裡有個sentinel,有人稱為微服務哨兵。它是一套更完整的生態,除了我上面提到的功能之外,還提供了動態系統保護、熱點限流等功能。