guava限流器RateLimiter原理及源码分析
來源:https://www.cnblogs.com/zhandouBlog/p/11743660.html
前言
RateLimiter是基于令牌桶算法實(shí)現(xiàn)的一個多線程限流器,它可以將請求均勻的進(jìn)行處理,當(dāng)然他并不是一個分布式限流器,只是對單機(jī)進(jìn)行限流。它可以應(yīng)用在定時拉取接口數(shù)據(jù),
預(yù)防單機(jī)過大流量使用。
原理
首先先講一下令牌桶的原理,每隔一段時間生產(chǎn)一個令牌放入桶里,請求在執(zhí)行時需要拿到令牌才可以執(zhí)行,如果拿不到令牌將等待令牌產(chǎn)生,一個生產(chǎn)者,多個消費(fèi)者。
但是這樣的令牌桶有一個問題,如果CPU負(fù)載過高,生產(chǎn)令牌的線程沒有獲取到時間片生產(chǎn)令牌,那么限制的流量將會比設(shè)定值更低。
可能是出于這個原因,guava并沒有這樣做,而是一個惰性生產(chǎn)令牌,每次請求令牌時,通過當(dāng)前時間和下次產(chǎn)生令牌時間的差值計算出現(xiàn)在有多少個令牌,如果當(dāng)前時間比發(fā)放時間大,會獲得令牌,并且會生成令牌存儲。如果令牌不夠,則讓線程sleep,并且將下次令牌產(chǎn)生時間更新成當(dāng)前時間+sleep時間
sleep,并且將下次發(fā)放令牌的時間,設(shè)置成當(dāng)前時間+線程sleep的時間。這樣說,可能不是很清楚,看圖。
?這樣做的好處是什么,如果獲取令牌的線程搶不到cpu,只是這個線程的執(zhí)行時間會晚,其他線程不會受到影響。
源碼閱讀
| public static void main(String[] args) {RateLimiter rateLimiter = RateLimiter.create(10);while (true) {long start = System.currentTimeMillis();rateLimiter.acquire();System.out.println(System.currentTimeMillis() - start);}} |
?運(yùn)行可以發(fā)現(xiàn),上面的代碼除了第一次輸出的是0或者1,其他都接近100。下面先看一下RateLimiter.create做了哪些事情
| static RateLimiter create(double permitsPerSecond, SleepingStopwatch stopwatch) {//創(chuàng)建對象,并且賦值,permitsPerSecond這個是我們設(shè)置的qps,stopwatch這個相當(dāng)于一個計時器,記錄相對時間,類似于我上面圖中的10ms,100ms等,下面?zhèn)魅氲?.0就是一秒的意思,設(shè)置上速率就是一秒多少次RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0);rateLimiter.setRate(permitsPerSecond);return rateLimiter;} 看一下setRate public final void setRate(double permitsPerSecond) {checkArgument(permitsPerSecond > 0.0 && !Double.isNaN(permitsPerSecond), "rate must be positive");//從名字就可以看出這是一個互斥鎖,這個互斥鎖采用了double check懶漢單例模式生成,synchronized (mutex()) {doSetRate(permitsPerSecond, stopwatch.readMicros());}} |
下面看一下doSetRate,真正開始設(shè)置速率了
| final void doSetRate(double permitsPerSecond, long nowMicros) {//這個方法非常重要里面是nextFreeTicketMicros和storedPermits的設(shè)置,在生成對象的時候沒有用,獲取令牌時再講resync(nowMicros);//這個就是令牌生成間隔,微秒表示double stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;this.stableIntervalMicros = stableIntervalMicros;這里又有一個doSetRatedoSetRate(permitsPerSecond, stableIntervalMicros);}void doSetRate(double permitsPerSecond, double stableIntervalMicros) {double oldMaxPermits = this.maxPermits;//設(shè)置最大令牌maxPermits = maxBurstSeconds * permitsPerSecond;//設(shè)置存儲令牌if (oldMaxPermits == Double.POSITIVE_INFINITY) {storedPermits = maxPermits;} else {storedPermits =(oldMaxPermits == 0.0)? 0.0 // initial state: storedPermits * maxPermits / oldMaxPermits;} |
到了這里整個創(chuàng)建過程就結(jié)束了,基本上就是一些設(shè)置,創(chuàng)建了鎖,設(shè)置了生成令牌的間隔時間等等,下面看一下獲取令牌的方法。
| //獲取一個令牌 public double acquire() {return acquire(1);}public double acquire(int permits) {//reserve返回等待時間,內(nèi)部進(jìn)行了令牌的獲取long microsToWait = reserve(permits);stopwatch.sleepMicrosUninterruptibly(microsToWait);return 1.0 * microsToWait / SECONDS.toMicros(1L);}final long reserve(int permits) {checkPermits(permits);//創(chuàng)建對象時生成的鎖synchronized (mutex()) {//stopwatch.readMicros()拿到當(dāng)前的時間,預(yù)訂return reserveAndGetWaitLength(permits, stopwatch.readMicros());}}final long reserveAndGetWaitLength(int permits, long nowMicros) {//將數(shù)據(jù)透傳,拿到最早可預(yù)訂的時間,如果預(yù)訂時間在未來時間,返回一個大于0的值為等待時間long momentAvailable = reserveEarliestAvailable(permits, nowMicros);return max(momentAvailable - nowMicros, 0);} |
下面的代碼就是我圖中的實(shí)現(xiàn)
| final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {//這個方法很重要先看下面這個方法的講解,在從這里往下看resync(nowMicros);//返回下次發(fā)放令牌時間,如果這個時間大于當(dāng)前時間,在調(diào)用的上層會sleeplong returnValue = nextFreeTicketMicros;//拿到此次花費(fèi)的令牌double storedPermitsToSpend = min(requiredPermits, this.storedPermits);// 如果令牌不夠,這里就會大于0,下面就會得出一個等待時間double freshPermits = requiredPermits - storedPermitsToSpend;long waitMicros =storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)+ (long) (freshPermits * stableIntervalMicros);//將下次發(fā)放令牌的時間加上等待時間this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);this.storedPermits -= storedPermitsToSpend;return returnValue;}void resync(long nowMicros) {if (nowMicros > nextFreeTicketMicros) {//當(dāng)前時間大于下次令牌發(fā)放時間,新的令牌為當(dāng)前時間減去下次發(fā)放令牌時間除以生成令牌的時間間隔double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();//不能超過最大令牌數(shù)storedPermits = min(maxPermits, storedPermits + newPermits);//更新下次發(fā)放令牌時間為當(dāng)前時間nextFreeTicketMicros = nowMicros;}} |
總結(jié)
RateLimiter的原理用語言描述,很容易把人繞暈,上面的圖其實(shí)是最好的總結(jié),懂得原理才能更好的使用,在多種限流器中選擇合適的限流器。了解源碼,能更進(jìn)一步的掌握原理,并且從源碼中可以學(xué)到設(shè)計思路和
一些設(shè)計模式的應(yīng)用。
總結(jié)
以上是生活随笔為你收集整理的guava限流器RateLimiter原理及源码分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PostgreSQL 的一个简单连接和查
- 下一篇: JMM同步原语之final域的内存语义