RateLimiter:做爲抽象類提供一個限流器的基本的抽象方法。
SmoothRateLimiter:平滑限流器實現,提供了Ratelimiter中的抽象限流方法的平滑實現。
SmoothBursty:容許突發流量的平滑限流器的實現。
SmoothWarmingUp:平滑預熱限流器的實現。函數
public void test() throws InterruptedException { RateLimiter rateLimiter = RateLimiter.create(2); while (true){ System.out.println(rateLimiter.acquire(2)); TimeUnit.SECONDS.sleep(2); System.out.println(rateLimiter.acquire(1)); System.out.println(rateLimiter.acquire(1)); System.out.println(rateLimiter.acquire(10)); } }
執行結果
ui
acquire方法返回結果表明獲取token所等待的時間。this
第一行0等待,剛建立限流器,還沒來得及聽任何token,此處存儲的token=0,可是無欠款因此預消費2個;
sleep 2秒,按照每秒2個的速度,先「還」了欠款,而後token直接恢復至max = 2;
第二行0,現有2個token,用一個,無需等待。
第三行0,現有1個token,用一個,無需等待。
第四行0,現有0個token,無欠款,直接借10個。
第五行4.999868,幫上一個還欠款,等待5秒直到還完欠款後,又借了2個。
重複第一行......spa
在使用RateLimiter.create(double)方法初始化限流器時,實際上默認使用的是SmoothBurstypwa
public static RateLimiter create(double permitsPerSecond) { return create(permitsPerSecond, SleepingStopwatch.createFromSystemTimer()); } static RateLimiter create(double permitsPerSecond, SleepingStopwatch stopwatch) { RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */); rateLimiter.setRate(permitsPerSecond); return rateLimiter; }
/** The currently stored permits. 當前存儲的令牌數 */ double storedPermits; /** * The maximum number of stored permits. * 最大存儲令牌數 */ double maxPermits; /** * The interval between two unit requests, at our stable rate. E.g., a stable rate of 5 permits * per second has a stable interval of 200ms. * 添加令牌時間間隔 */ double stableIntervalMicros; /** * The time when the next request (no matter its size) will be granted. After granting a request, * this is pushed further in the future. Large requests push this further than small requests. * 下一次請求能夠獲取令牌的起始時間 * 因爲RateLimiter容許預消費,上次請求預消費令牌後 * 下次請求須要等待相應的時間到nextFreeTicketMicros時刻才能夠獲取令牌 */ private long nextFreeTicketMicros = 0L; // could be either in the past or future
從acquire()函數開始線程
public double acquire() { return acquire(1);//默認取一個令牌 } public double acquire(int permits) { long microsToWait = reserve(permits);//從限流器中獲取指定的令牌,並返回須要等待的時間 stopwatch.sleepMicrosUninterruptibly(microsToWait);//讓「鬧鐘」將當前線程中止睡眠指定時間 return 1.0 * microsToWait / SECONDS.toMicros(1L);//返回等待的時間,單位是秒 } final long reserve(int permits) { checkPermits(permits);//參數校驗 synchronized (mutex()) {//獲取鎖,多個請求到達,須要串行的獲取 return reserveAndGetWaitLength(permits, stopwatch.readMicros()); } }
先來看下加鎖的邏輯code
private volatile Object mutexDoNotUseDirectly; private Object mutex() { Object mutex = mutexDoNotUseDirectly; if (mutex == null) { synchronized (this) { mutex = mutexDoNotUseDirectly; if (mutex == null) { mutexDoNotUseDirectly = mutex = new Object(); } } } return mutex; }
典型的雙重檢查單例
接着繼續探索獲取令牌的邏輯代碼orm
final long reserveAndGetWaitLength(int permits, long nowMicros) { long momentAvailable = reserveEarliestAvailable(permits, nowMicros);//獲取token並返回下個請求能夠來獲取token的時間 return max(momentAvailable - nowMicros, 0);//計算等待時間 }
關鍵函數一:
abstract long reserveEarliestAvailable(int permits, long nowMicros);
SmoothRateLimiter實現了它,是獲取token、消耗token的主流程token
final long reserveEarliestAvailable(int requiredPermits, long nowMicros) { resync(nowMicros); long returnValue = nextFreeTicketMicros; double storedPermitsToSpend = min(requiredPermits, this.storedPermits); double freshPermits = requiredPermits - storedPermitsToSpend; long waitMicros = **加粗文字** storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend) + (long) (freshPermits * stableIntervalMicros); this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros); this.storedPermits -= storedPermitsToSpend; System.out.println(String.format("storedPermitsToSpend=%s,freshPermits=%s,waitMicros=%s,storedPermits=%s", storedPermitsToSpend, freshPermits, waitMicros, storedPermits)); return returnValue; }
storedPermitsToSpend表明須要從storedPermits扣除的token,若是storedPermits已經=0了,那麼不會扣除到負數
waitMicros表明這次預消費的令牌須要多少時間來恢復,最終將它加到nextFreeTicketMicros**
那麼SmoothBursty是怎麼實現預消費的呢?,讓咱們先看下更新token的邏輯,即void resync(long nowMicros)圖片
void resync(long nowMicros) { // if nextFreeTicket is in the past, resync to now if (nowMicros > nextFreeTicketMicros) { double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros(); storedPermits = min(maxPermits, storedPermits + newPermits); nextFreeTicketMicros = nowMicros; } }
更新流程
SmoothBursty是怎麼實現預消費的呢?
其實,只要保證一點就能夠進行預消費,即無欠款,無欠款就表明當前時間大於等於nextFreeTime,SmoothBursty就是依靠此原理來處理突發的流量。
先看下示例代碼
public void test_warmingUp(){ RateLimiter rateLimiter = RateLimiter.create(2, 4, TimeUnit.SECONDS); while (true){ System.out.println(rateLimiter.acquire(1)); System.out.println(rateLimiter.acquire(1)); System.out.println(rateLimiter.acquire(1)); System.out.println(rateLimiter.acquire(1)); } }
運行效果
0.0 1.372264 1.117788 0.869905 0.620505 0.496059 0.495301 0.496027 0.495794
SmoothWarmingUp爲了預防忽然暴增的流量將系統壓垮,很貼心的增長了「預熱」。指定的warmupPeriod就是預熱時間,在「冷狀態」即沒有流量進入時,放入每一個token的時間不單單是1/permitsPerSecond,還要加上一個預熱時間,類註釋上的圖做了很好的解釋。
SmoothWarmingUp在初始階段與SmoothBursty有點不一樣,SmoothWarmingUp初始storePermits = maxPermits。一直使用permits直至storePermits減小到thresholdPermits(setRate調用時計算)放入token的時間便穩定下來,到達了「熱狀態」,此時與SmoothBursty是如出一轍。可是若是在warmupPeriod時間內沒有流量進入,則再次會進入「冷狀態「。
在實現上SmoothWarmingUp與SmoothBursty基本相同,惟一不一樣僅僅只有兩個函數的實現上
SmoothWarmingUp的註釋解釋的很到位,在預熱限流器中,計算token的等待時間就能夠轉化計算圖中的面積,你們能夠順着註釋推導一下。
SmoothBursty:初始token爲0,容許預消費,放入token的時間固定爲1/permitsPerSecond.(一開始直接上)
SmoothWarmingUp:初始token爲MaxPermits,容許預消費,能夠指定預熱時間,在與預熱時間事後速率恢復平穩與SmoothBursty一致。(老司機有前戲)
SmoothWarmingUp像了改良版的SmoothBursty,有個預熱時間,系統能更加從容的應付流量的來襲,所以通常能夠優先使用SmoothWarmingUp。