服務限流原理及演算法

王若伊_恩賜解脫發表於2021-08-25

限流是啥?
維基百科是這樣解釋的:
在計算機網路中,頻率限制被應用在控制網路介面收到或傳送的請求頻次,它可以被用來阻止dos攻擊或者是網路爬蟲。
直白點說,就是限制服務收到或發出的請求頻次,保證整體服務可以正常健康的使用。
談到這裡有人會想,只要我服務處理的速度足夠快,那麼頻次高點也沒問題,而且我們做的系統不就應該接受各種峰值考驗麼?
這麼想沒問題,提高服務的吞吐量確實是沒問題,但是有時出於安全形度考慮,比如爬蟲,就不應該讓他過快的獲取到抓取的資料,還可能涉及到洗錢網路安全什麼的。
另外一方面,需求的開發是有成本的,不應該想當然,在超出系統本來承受打範圍後,還有數量級差異的請求進來,這種需求改進的成本無論是人工還是費用都是巨大的。
因此限流是肯定有存在必要的。下面我們來談談經典的限流都有哪些:
假設我們要實現1分鐘,100次的限流:
1、計數器限流(固定視窗限流)
這個演算法簡單實用,基本可以滿足很多業務場景的需要
大致原理是這樣的,有一個全域性的計數器,初始值為0,每次請求進來,計數器+1,然後再處理,如果計數器超過100,則不再處理請求,直接返回。
同時我們啟動一個定時的執行緒,每過1分鐘,就重置計數器為0;
流程大概是下邊這個樣子:

但是這種計數器限流有一個臨界的問題,我們初衷其實上是,任意的一分鐘範圍內,處理的請求不超過100。
但是假若第一分鐘最後10s處理100個請求,第二分鐘最初10s處理100個請求,也就是說20s內,我們共處理了200請求,這顯然是不滿足最初我們的訴求的。
2、滑動視窗
為了解決傳統計數器限流的弊端,有人提出了滑動視窗的概念。所謂的滑動視窗,是指不存在固定的起止、結束的時間點。而是當前的時間點向前推動單位週期,判定流量是否超限:(防盜連線:本文首發自http://www.cnblogs.com/jilodream/ )
舉個例子,比如1min,100次的限流
我們將時間劃分成10s每個單位
請求如下圖,在視窗1期間,請求已經到達60次,因此後兩個時間單位內就進入限流狀態。

但是隨著時間推移,視窗2期間的請求次數再次小於了100,又可以處理請求。
繼續推移,視窗3的請求次數再次到達100,因此又被限流。

滑動視窗如何實現呢?
我們可以使用一個HashMap或者乾脆使用一個陣列,然後統計每個時間單位內的請求,當判斷是否限流時,只要統計時間窗內的請求次數是否滿足即可。
同時map或者是陣列我們並不需要無限大,我們可以反覆利用其中已經過期的位置。另外滑動視窗的單位時間越小,整體的限流也就會越精準。

3、漏桶演算法
滑動視窗的結尾我們有說時間單位越小,那麼就越精準平滑,可是我們並不能把時間單位搞到無限小,或者說盡可能小的時候就出現偏差,比如1ns,但是可能你剛計算完限流後,已經到下一個時間單位了
為了解決這種問題,有人提出了漏桶演算法。
如圖,這個模型和訊息佇列有點像:


請求進來後,會先放置在漏桶中,漏桶最多儲存的請求是有限的。當漏桶滿了以後,會丟棄請求,否則會新增到漏桶的隊尾。
同時任務處理會恆定的處理漏桶佇列中的請求。以此達到平衡。漏桶演算法也並不一定需要一個列隊,我們也可以使用一個計數器,當請求進來後,計數器+1。計數器滿了以後會請求丟棄。同時任務處理完畢後會回撥計數器-1;(防盜連線:本文首發自http://www.cnblogs.com/jilodream/ )

4、令牌桶演算法
漏桶演算法會讓請求更加平滑,但是當有突發請求衝擊時,並且我們的下游處理速度又比較快(或者是使用固定頻率)來處理,這樣我們沒有辦法很好的控制請求的處理速率。
如果你設定固定頻率,當某個時段請求衝擊比較大,而我們也應該滿足時,顯然無法滿足。當放開請求頻率,又可能因為下游處理速度太快,對機器的整體效能造成衝擊,而且我們又無法控制。
針對這種情況,有人提供了令牌桶演算法:
如圖

1、有執行緒會按照固定頻率向令牌桶中新增令牌,當令牌桶滿了之後,就不會繼續新增。
2、請求來了之後,向令牌桶中請求令牌,成功拿到令牌可以繼續處理請求
3、未請求到令牌,則丟棄該請求

不知道大家有沒有是否記得有一款騰訊的小遊戲叫開心消消樂。其中的防沉迷策略就是通過令牌桶的演算法處理的。玩家每次玩遊戲會消耗一份閃電(令牌),可以連續玩,直到所有閃電都消耗完畢。
同時隨著時間的推移,閃電又會慢慢變滿,但是最多不會超過上限(令牌桶滿)。在這個增加的過程中,我們也可以同時去玩消消樂消耗令牌

相關文章