摘要:允許突發(fā)流量的平滑限流器的實(shí)現(xiàn)。實(shí)例執(zhí)行結(jié)果方法返回結(jié)果代表獲取所等待的時(shí)間。先看下示例代碼運(yùn)行效果為了預(yù)防突然暴增的流量將系統(tǒng)壓垮,很貼心的增加了預(yù)熱。
RateLimiter 類圖
RateLimiter:作為抽象類提供一個(gè)限流器的基本的抽象方法。
SmoothRateLimiter:平滑限流器實(shí)現(xiàn),提供了Ratelimiter中的抽象限流方法的平滑實(shí)現(xiàn)。
SmoothBursty:允許突發(fā)流量的平滑限流器的實(shí)現(xiàn)。
SmoothWarmingUp:平滑預(yù)熱限流器的實(shí)現(xiàn)。
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)); } }
執(zhí)行結(jié)果
acquire方法返回結(jié)果代表獲取token所等待的時(shí)間。
第一行0等待,剛創(chuàng)建限流器,還沒來得及放任何token,此處存儲(chǔ)的token=0,但是無欠款所以預(yù)消費(fèi)2個(gè);
sleep 2秒,按照每秒2個(gè)的速度,先“還”了欠款,然后token直接恢復(fù)至max = 2;
第二行0,現(xiàn)有2個(gè)token,用一個(gè),無需等待。
第三行0,現(xiàn)有1個(gè)token,用一個(gè),無需等待。
第四行0,現(xiàn)有0個(gè)token,無欠款,直接借10個(gè)。
第五行4.999868,幫上一個(gè)還欠款,等待5秒直到還完欠款后,又借了2個(gè)。
重復(fù)第一行......
在使用RateLimiter.create(double)方法初始化限流器時(shí),實(shí)際上默認(rèn)使用的是SmoothBursty
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; }SmoothBursty
/** The currently stored permits. 當(dāng)前存儲(chǔ)的令牌數(shù) */ double storedPermits; /** * The maximum number of stored permits. * 最大存儲(chǔ)令牌數(shù) */ 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. * 添加令牌時(shí)間間隔 */ 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. * 下一次請求可以獲取令牌的起始時(shí)間 * 由于RateLimiter允許預(yù)消費(fèi),上次請求預(yù)消費(fèi)令牌后 * 下次請求需要等待相應(yīng)的時(shí)間到nextFreeTicketMicros時(shí)刻才可以獲取令牌 */ private long nextFreeTicketMicros = 0L; // could be either in the past or future
從acquire()函數(shù)開始
public double acquire() { return acquire(1);//默認(rèn)取一個(gè)令牌 } public double acquire(int permits) { long microsToWait = reserve(permits);//從限流器中獲取指定的令牌,并返回需要等待的時(shí)間 stopwatch.sleepMicrosUninterruptibly(microsToWait);//讓“鬧鐘”將當(dāng)前線程停止睡眠指定時(shí)間 return 1.0 * microsToWait / SECONDS.toMicros(1L);//返回等待的時(shí)間,單位是秒 } final long reserve(int permits) { checkPermits(permits);//參數(shù)校驗(yàn) synchronized (mutex()) {//獲取鎖,多個(gè)請求到達(dá),需要串行的獲取 return reserveAndGetWaitLength(permits, stopwatch.readMicros()); } }
先來看下加鎖的邏輯
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; }
典型的雙重檢查單例
接著繼續(xù)探索獲取令牌的邏輯代碼
final long reserveAndGetWaitLength(int permits, long nowMicros) { long momentAvailable = reserveEarliestAvailable(permits, nowMicros);//獲取token并返回下個(gè)請求可以來獲取token的時(shí)間 return max(momentAvailable - nowMicros, 0);//計(jì)算等待時(shí)間 }
關(guān)鍵函數(shù)一:
abstract long reserveEarliestAvailable(int permits, long nowMicros);
SmoothRateLimiter實(shí)現(xiàn)了它,是獲取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; }
更新令牌桶中的token。
計(jì)算下次能獲得令牌的時(shí)間
扣除本次所需令牌
storedPermitsToSpend代表需要從storedPermits扣除的token,如果storedPermits已經(jīng)=0了,那么不會(huì)扣除到負(fù)數(shù)
waitMicros代表此次預(yù)消費(fèi)的令牌需要多少時(shí)間來恢復(fù),最終將它加到nextFreeTicketMicros**
那么SmoothBursty是怎么實(shí)現(xiàn)預(yù)消費(fèi)的呢?,讓我們先看下更新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; } }
更新流程
如果當(dāng)前時(shí)間大于freeTime,則進(jìn)入更新操作。
將時(shí)間差除以令牌恢復(fù)間隔,計(jì)算出得到恢復(fù)的令牌個(gè)數(shù)
更新令牌桶令牌的存儲(chǔ)數(shù)量和freeTime
SmoothBursty是怎么實(shí)現(xiàn)預(yù)消費(fèi)的呢?
其實(shí),只要保證一點(diǎn)就可以進(jìn)行預(yù)消費(fèi),即無欠款,無欠款就代表當(dāng)前時(shí)間大于等于nextFreeTime,SmoothBursty就是依靠此原理來處理突發(fā)的流量。
先看下示例代碼
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)); } }
運(yùn)行效果
0.0 1.372264 1.117788 0.869905 0.620505 0.496059 0.495301 0.496027 0.495794
SmoothWarmingUp為了預(yù)防突然暴增的流量將系統(tǒng)壓垮,很貼心的增加了“預(yù)熱”。指定的warmupPeriod就是預(yù)熱時(shí)間,在“冷狀態(tài)”即沒有流量進(jìn)入時(shí),放入每個(gè)token的時(shí)間不僅僅是1/permitsPerSecond,還要加上一個(gè)預(yù)熱時(shí)間,類注釋上的圖作了很好的解釋。
SmoothWarmingUp在初始階段與SmoothBursty有點(diǎn)不同,SmoothWarmingUp初始storePermits = maxPermits。一直使用permits直至storePermits減少到thresholdPermits(setRate調(diào)用時(shí)計(jì)算)放入token的時(shí)間便穩(wěn)定下來,到達(dá)了“熱狀態(tài)”,此時(shí)與SmoothBursty是一模一樣。但是如果在warmupPeriod時(shí)間內(nèi)沒有流量進(jìn)入,則再次會(huì)進(jìn)入“冷狀態(tài)“。
在實(shí)現(xiàn)上SmoothWarmingUp與SmoothBursty基本相同,唯一不同僅僅只有兩個(gè)函數(shù)的實(shí)現(xiàn)上
coolDownIntervalMicros()返回一個(gè)token的冷卻時(shí)間,SmoothWarmingUp注釋中有介紹,為了保證在warmUpPeriod時(shí)間剛好可以恢復(fù)maxPermits個(gè)token,因此SmoothWarmingUp此函數(shù)返回的是warmupPeriodMicros / maxPermits
storedPermitsToWaitTime(double storedPermits, double permitsToTake)返回消耗permitsToTake個(gè)token所需要等待的時(shí)間,SmoothBursty則是直接返回0.
SmoothWarmingUp的注釋解釋的很到位,在預(yù)熱限流器中,計(jì)算token的等待時(shí)間就可以轉(zhuǎn)化計(jì)算圖中的面積,大家可以順著注釋推導(dǎo)一下。
總結(jié)SmoothBursty:初始token為0,允許預(yù)消費(fèi),放入token的時(shí)間固定為1/permitsPerSecond.(一開始直接上)
SmoothWarmingUp:初始token為MaxPermits,允許預(yù)消費(fèi),可以指定預(yù)熱時(shí)間,在與預(yù)熱時(shí)間過后速率恢復(fù)平穩(wěn)與SmoothBursty一致。(老司機(jī)有前戲)
SmoothWarmingUp像了改良版的SmoothBursty,有個(gè)預(yù)熱時(shí)間,系統(tǒng)能更加從容的應(yīng)付流量的來襲,因此一般可以優(yōu)先使用SmoothWarmingUp。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/76815.html
摘要:有如下模塊源碼解析源碼解析源碼解析源碼解析源碼解析源碼解析源碼解析源碼解析源碼解析使用和監(jiān)控和博客從到學(xué)習(xí)介紹從到學(xué)習(xí)上搭建環(huán)境并構(gòu)建運(yùn)行簡單程序入門從到學(xué)習(xí)配置文件詳解從到學(xué)習(xí)介紹從到學(xué)習(xí)如何自 Flink Metrics 有如下模塊: Flink Metrics 源碼解析 —— Flink-metrics-core Flink Metrics 源碼解析 —— Flink-metr...
摘要:機(jī)制博客從到學(xué)習(xí)介紹從到學(xué)習(xí)上搭建環(huán)境并構(gòu)建運(yùn)行簡單程序入門從到學(xué)習(xí)配置文件詳解從到學(xué)習(xí)介紹從到學(xué)習(xí)如何自定義從到學(xué)習(xí)介紹從到學(xué)習(xí)如何自定義從到學(xué)習(xí)轉(zhuǎn)換從到學(xué)習(xí)介紹中的從到學(xué)習(xí)中的幾種詳解從到學(xué)習(xí)讀取數(shù)據(jù)寫入到從到學(xué)習(xí)項(xiàng)目如何運(yùn)行從 Flink Checkpoint 機(jī)制 https://t.zsxq.com/ynQNbeM 博客 1、Flink 從0到1學(xué)習(xí) —— Apache Fl...
摘要:序列化機(jī)制博客從到學(xué)習(xí)介紹從到學(xué)習(xí)上搭建環(huán)境并構(gòu)建運(yùn)行簡單程序入門從到學(xué)習(xí)配置文件詳解從到學(xué)習(xí)介紹從到學(xué)習(xí)如何自定義從到學(xué)習(xí)介紹從到學(xué)習(xí)如何自定義從到學(xué)習(xí)轉(zhuǎn)換從到學(xué)習(xí)介紹中的從到學(xué)習(xí)中的幾種詳解從到學(xué)習(xí)讀取數(shù)據(jù)寫入到從到學(xué)習(xí)項(xiàng)目如何 Flink 序列化機(jī)制 https://t.zsxq.com/JaQfeMf 博客 1、Flink 從0到1學(xué)習(xí) —— Apache Flink 介紹 2...
摘要:模塊中的類結(jié)構(gòu)如下博客從到學(xué)習(xí)介紹從到學(xué)習(xí)上搭建環(huán)境并構(gòu)建運(yùn)行簡單程序入門從到學(xué)習(xí)配置文件詳解從到學(xué)習(xí)介紹從到學(xué)習(xí)如何自定義從到學(xué)習(xí)介紹從到學(xué)習(xí)如何自定義從到學(xué)習(xí)轉(zhuǎn)換從到學(xué)習(xí)介紹中的從到學(xué)習(xí)中的幾種詳解從到學(xué)習(xí)讀取數(shù)據(jù)寫入到從到學(xué) Flink-Client 模塊中的類結(jié)構(gòu)如下: https://t.zsxq.com/IMzNZjY showImg(https://segmentfau...
摘要:模塊中的類結(jié)構(gòu)如下博客從到學(xué)習(xí)介紹從到學(xué)習(xí)上搭建環(huán)境并構(gòu)建運(yùn)行簡單程序入門從到學(xué)習(xí)配置文件詳解從到學(xué)習(xí)介紹從到學(xué)習(xí)如何自定義從到學(xué)習(xí)介紹從到學(xué)習(xí)如何自定義從到學(xué)習(xí)轉(zhuǎn)換從到學(xué)習(xí)介紹中的從到學(xué)習(xí)中的幾種詳解從到學(xué)習(xí)讀取數(shù)據(jù)寫入到從到學(xué) Flink-Annotations 模塊中的類結(jié)構(gòu)如下: https://t.zsxq.com/f6eAu3J showImg(https://segme...
閱讀 1915·2023-04-26 02:51
閱讀 2966·2021-09-10 10:50
閱讀 3234·2021-09-01 10:48
閱讀 3753·2019-08-30 15:53
閱讀 1916·2019-08-29 18:40
閱讀 470·2019-08-29 16:16
閱讀 2102·2019-08-29 13:21
閱讀 1874·2019-08-29 11:07