穩定性五件套-限流的原理和實現

程式設計一生發表於2020-06-02

背景

 

最近了解到很多朋友對限流、熔斷、降級、隔離、超時重試的概念和應用場景理解的不是很到位,所以想用五篇的篇幅稍微系統的介紹一下。

 

本篇是第一篇,是限流做詳解,如果反饋好的話,我會繼續寫下面四篇。不好的話就算了,算我理解不夠,再自己總結總結。

 

限流的概念

 

有朋友問我限流和熔斷有什麼區別,我的理解很簡單。限流作用是防禦上游流量超過處理能力的手段,熔斷作用是容錯下游的快速失敗手段。

 

舉個生活中的限流例子:

 

小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,有人稱為微服務哨兵。它是一套更完整的生態,除了我上面提到的功能之外,還提供了動態系統保護、熱點限流等功能。

 

相關文章