Guava RateLimiter 原理 理解

背景

項目中限流可以用Guava RateLimiter,不想了解原理是啥嗎?!
這篇文章已經寫的很好了
沒啥可補充了,加一些個人思考吧

思考

如果讓你來造一個限流器,有啥想法?

直觀想法1,對應參考文章的漏桶算法

就是用一個固定大小的隊列。比如設置限流爲5qps,1s可以接受5個請求;那我們就造一個大小爲5的隊列,如果隊列爲滿了,就拒絕請求;如果隊列未滿,就往隊列添加請求。

如何控制速率呢,我們通過控制消費者的消費速率是5qps,1s消費5個即可。

問題,說的挺輕巧,具體怎麼控制消費者的速率呢?又加一個定時任務來消費隊列嗎,也挺費勁的。

想法2,對應參考文章的令牌算法

令牌聽起來挺酷的。以固定的速率往桶裏發放令牌。然後消費者每次要取到令牌(acquire)纔可以響應請求

優點:由於令牌是固定間隔發放的,假設還是5qps,如果我有1s內沒有請求,我的令牌桶就滿了,可以一瞬間響應5個請求(一次過取5個令牌),也就是可以應對瞬時流量。

那麼這裏也涉及一個固定間隔發放的問題,難道也是需要定時任務往”桶裏“放令牌嗎?

那我們來看下Guava怎麼搞的,先看下文章中的例子比較好

假設限流爲2qps,那麼固定發放令牌的時間stableIntervalMicros就是500ms,初始化的storedPermits當前桶裏的令牌數是0。

在這裏插入圖片描述
在這裏插入圖片描述

操作說明 requiredPermits請求多少令牌 storedPermits桶裏還有多少令牌 nextFreeTicketMicros下一次能獲得令牌的時間 當前時間
初始化 none 0 0 0
直接獲取10個令牌,直接預支付成功,獲取成功 10 0 0 + 500 * 10 = 5000 0
按照上一步的計算,下次可以獲得令牌的時間是5000ms,過了5000ms,sleep睡醒了,這時候再執行acquire(1) 1 0 5000+500*1=5500 5000

debug看下分析對不對:
在這裏插入圖片描述

第1次獲取10個令牌

nowMicro是剛開始運行的時間,是一個很小的數,約等於0;
resync(nowMicro),更新令牌數,由於nowMicro約等於0,其實令牌數不會更新((0-0)/5000 = 0),令牌數還是0(約等於0)
storedPermitsToSpend,其實當前並沒有令牌,所以取min,約等於0;
freshPermits,需要預支付10個令牌,約等於10;
預支付之後需要等待10*interval = 10 * 500 ,約等於5000ms,5000000微秒
this.nextFreeTicketMicros 需要加上 waitMicros 也就是 下一次可以獲得令牌的時間是5000ms之後。

所以我們看到輸出信息的第一行在第0s獲取了10個令牌之後,下一次再想獲取1個令牌需要等待5000ms也就是5s。

第2次獲取1個令牌

然後再一次想獲取1個令牌,當前時間還是約等於0,這時候resync,nowMicros(0)比nextFreeTicketMicros(5000)小,令牌不更新。returnValue=5000,storedPermitsToSpend=0,freshPermits=1,需要再等 waitMicros=1 * 500ms,然後nextFreeTicketMicros更新爲5000+500=5500,返回returnValue=5000;外層函數睡眠5000ms,返回5000(輸出打印獲取1個token,約5s)

第3次獲取10個令牌

上面說的,睡了5000ms,當前時間nowMicros=5000; resync,nowMicros(5000)比nextFreeTicketMicros(5500)小,令牌不更新,還是欠費狀態,只能預支付。returnValue=5500, storedPermitsToSpend=0,freshPermits=10,需要預支付10個令牌, waitMicros=10 * 500ms = 5000,然後nextFreeTicketMicros更新爲5500+5000=10500,返回returnValue=5500;外層函數睡眠5500-5000=500ms,返回500(輸出打印獲取10個token,約0.5s)

小結

所以咱們這個令牌算法的思路是怎樣的呢?

維護一個nextFreeTicketMicros,記錄下一次可以獲得令牌的時間;

每次來acquire請求,都會先更新令牌數( (當前時間-nextFreeTicketMicros)/間隔時間,同時不能大於最大值 ),然後計算waitMicros = 設定間隔 * 請求數, 並將 nextFreeTicketMicros += waitMicros,然後sleep,並返回成功。

高併發的情況

高併發的情況下是怎麼樣的呢?我們會在每個請求裏用acuiqre()來限流,每次只獲取一個令牌,那麼假設1s內來了10個請求,只有前2個可以acquire成功,第3個會sleep 500*1, 第4個會sleep 500 * 2, 第5個會sleep 500 * 3,以此類推。

總結

再難啃的代碼,一行一行debug,用腦子當計算機過幾遍,總會有一點理解的,不要害怕不要虛,奧利給!

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章