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