Guava之RateLimiter的设计
Guava源碼中很詳盡的解釋了RateLimiter的概念。
從概念上看,限流器以配置速率釋放允許的請(qǐng)求(permit)。如有必要,調(diào)用acquire()將會(huì)阻塞知道一個(gè)允許可用。一旦被獲取(acquired),允許(permits)將不必釋放。
限流器在并發(fā)環(huán)境中是安全的:它限制所有線程總的調(diào)用速率。但是,值得注意的是,它難以保證公平。
限流器經(jīng)常被用來(lái)限制一些物理或邏輯資源被訪問(wèn)的速率。經(jīng)常和它對(duì)比的是j.u.c.Semaphore,它限制了訪問(wèn)資源總的并發(fā)數(shù)。(并發(fā)數(shù)和速率緊密相關(guān),參見(jiàn)Little's Law)
限流器原始定義為許可被發(fā)布的速率。沒(méi)有多余的配置,許可將被以固定速率(——字面意義被定義為 許可/sec) 分發(fā)。通過(guò)調(diào)節(jié)獨(dú)立的許可之間的延遲,保證許可按照配置的速率平滑分發(fā)。
在限流器正式進(jìn)入穩(wěn)定速率前,通常允許限流器有一個(gè)短暫的預(yù)熱階段。在該階段,許可分發(fā)速率穩(wěn)步提升,直至預(yù)定到達(dá)速率為止。
舉例,想象我們有一組任務(wù)要執(zhí)行,但我們不想要每秒提交超過(guò)2個(gè)任務(wù)。
final RateLimiter rateLimiter = RateLimiter.create(2.0); // rate is 2 permits per second"void submitTasks(List<Runnable> tasks, Executor executor) {for (Runnable task : tasks) {rateLimiter.acquire(); // may wait executor.execute(task);}}另一個(gè)例子是,想象我們生產(chǎn)一組數(shù)據(jù)流,但是我們想以5kb/sec速率恒定接收它。這個(gè)想法可以以限流器方式實(shí)現(xiàn)。即,每個(gè)許可對(duì)應(yīng)一個(gè)字節(jié),指定(限流器)恒定的速率為每秒5000次許可。
final RateLimiter rateLimiter = RateLimiter.create(5000.0); // rate = 5000 permits per secondvoid submitPacket(byte[] packet) {rateLimiter.acquire(packet.length);networkService.send(packet);}注意,請(qǐng)求的許可數(shù)量不會(huì)影響到對(duì)請(qǐng)求本身的壓制。(acquire(1)會(huì)和acquire(1000)產(chǎn)生相同的效果),但是它會(huì)影響到對(duì)下一個(gè)請(qǐng)求的壓制作用。如果成本較大的任務(wù)在空閑時(shí)到達(dá)限流器,將會(huì)被立即允許。但這之后的請(qǐng)求將會(huì)遭受額外的限制,為上次高昂代價(jià)的任務(wù)買單。
?
下面是一個(gè)SmoothRateLimiter的設(shè)計(jì)原理:
限流器的基本提點(diǎn)是一個(gè)“穩(wěn)定的速率”,即在正常條件下的最大速率。未達(dá)到這個(gè)目的,限流器會(huì)根據(jù)需要壓制到達(dá)的請(qǐng)求。通過(guò)計(jì)算,限流器會(huì)確保到達(dá)請(qǐng)求等待合理的時(shí)間,以此達(dá)到壓制的目的。
維持一個(gè)速率(通常被指定為QPS)最簡(jiǎn)單的方法是記住上個(gè)被允許請(qǐng)求的最后時(shí)間戳,并保證在1/QPS時(shí)間內(nèi)不執(zhí)行請(qǐng)求。例如,QPS=5(每秒5個(gè)token),如果我們能夠確保自從上個(gè)請(qǐng)求后,在200ms內(nèi)沒(méi)有請(qǐng)求被允許執(zhí)行,那么我們將獲得一個(gè)想要的速率。如果一個(gè)請(qǐng)求在上個(gè)請(qǐng)求被放行100ms后到達(dá),那么我們需要等待額外的100ms。在這個(gè)速率下,15個(gè)新的許可耗時(shí)3秒鐘(例如對(duì)于請(qǐng)求 acquire(15))。
很重要的一點(diǎn),是能夠意識(shí)到限流器對(duì)過(guò)去只有很淺的記憶。它只會(huì)記住上一個(gè)請(qǐng)求。那如果限流器很久沒(méi)有被使用,然后一個(gè)請(qǐng)求突然到達(dá)并被立即允許怎么辦?這可能會(huì)有兩種情形,一種是資源利用不充分,另一種則是導(dǎo)致溢出,具體取決于沒(méi)有遵循預(yù)定速率的真實(shí)原因。
之前的利用不足意味多余的資源可被獲取。限流器應(yīng)該加速一段時(shí)間,以利用這些資源。速率適應(yīng)網(wǎng)絡(luò)(帶寬)很重要,過(guò)去的利用不足被解釋為“幾乎為空的緩沖”,可以被快速填補(bǔ)。
另一方面,過(guò)去的利用不足也可能意味著“服務(wù)器沒(méi)有準(zhǔn)備好處理將來(lái)的請(qǐng)求”,例如,緩存失效,請(qǐng)求更有可能會(huì)觸發(fā)耗時(shí)的操作(一個(gè)更極端的例子是,當(dāng)一個(gè)服務(wù)器剛剛被引導(dǎo),它更可能忙于自身的喚醒)。
為應(yīng)對(duì)以上場(chǎng)景,我們?cè)鎏硪环N維度。即“過(guò)去的利用不足”被建模為變量“storedPermits”。這個(gè)變量在沒(méi)有使用時(shí)為0,當(dāng)有大量使用時(shí),它可以增長(zhǎng)到maxStoredPermits。所以,請(qǐng)求會(huì)被函數(shù)acquire(permits)觸發(fā)許可,提供以下兩種類型的許可:
-stored permits(可獲取的已存許可)
-fresh permits(新的的許可)
工作原理如下:
對(duì)于一個(gè)限流器,每秒產(chǎn)生一個(gè)令牌。不使用限流器時(shí),我們都會(huì)給storedPermits加1。如果說(shuō)我們有10sec不使用限流器(例如預(yù)計(jì)請(qǐng)求在時(shí)刻X到來(lái),但在請(qǐng)求到來(lái)之前,我們?cè)赬+10。這也是上段所描述的點(diǎn)。)。因此storedPermits變?yōu)?0(假設(shè)maxStoredPermits>=10)。在這時(shí),一個(gè)人acquire(3)的請(qǐng)求到了。我們從已有的storedPermits拿出許可服務(wù)這個(gè)請(qǐng)求,并將許可數(shù)降至7.(這如何被解釋為壓制時(shí)間,將會(huì)在之后被詳細(xì)討論。)這之后,假設(shè)馬上有一個(gè)acquire(10)的請(qǐng)求到達(dá),我們用剩下所有的7個(gè)許可數(shù)來(lái)應(yīng)對(duì)這個(gè)請(qǐng)求,還有3個(gè)許可數(shù),我們需要通過(guò)刷新限流器新提供。
我們也已經(jīng)知道花費(fèi)在3個(gè)新的許可上的時(shí)間:如果速率是1令牌/sec,那么我們將花費(fèi)3秒。但是使用7個(gè)已存許可又是什么意思呢?正如上面所說(shuō),這里沒(méi)有固定答案。如果我們主要興趣在應(yīng)對(duì)資源利用不足上,我們想要存儲(chǔ)許可釋放比刷新許可快。因?yàn)槔貌蛔?#61;尚有未被占用的資源。如果我們主要興趣點(diǎn)在應(yīng)對(duì)溢出,那么存儲(chǔ)的許可數(shù)應(yīng)該釋放的比刷新的慢。因此,我們想要一個(gè)(在每種情形都不同的)方法來(lái)解釋storePermits,以此壓制時(shí)間。storedPermitsToWaitTime(double storedPermits, double permitsToTake) 在其中扮演重要角色。底層的模型是一個(gè)持續(xù)變化的函數(shù)映射storedPermits(從0到maxStoredPermits)到1/rate(時(shí)間間隔)。storedPermits在衡量未使用時(shí)間上是必不可少的。我們使用未利用時(shí)間換取許可數(shù)(permits)。速率是permits/time,因此1/rate=time/permits.因此"1/rate"(time/permits)乘以permits等于給定時(shí)間。對(duì)于指定數(shù)量的請(qǐng)求許可來(lái)說(shuō),這個(gè)積分函數(shù)(storedPermitsToWaitTime()計(jì)算)與持續(xù)請(qǐng)求的最小時(shí)間間隔相關(guān)。
這里有個(gè)storePermitsToWaitTime的例子。如果storedPermits=10,我們想要3個(gè)permits,我們從storedPermits中去獲取,減少他們到7個(gè),并且計(jì)算壓制時(shí)間作為一個(gè)調(diào)用storedPermitsToWaitTime(storedPermits=10,permitsToTake=3),這將會(huì)評(píng)估這個(gè)函數(shù)積分從7到10.
使用積分保證acquire(3)效果等同于3次acquire(1),或一次acquire(2)+一次acquire(1)。因?yàn)榉e分在[7.0,10.0]等同于在[7.0,8.0],[8.0,9.0],[9.0,10.0]等等。無(wú)論這個(gè)函數(shù)是什么。這使得我們可以正確處理不同權(quán)重(permits)的請(qǐng)求時(shí),不論真正的函數(shù)是什么。所以我們可以自由調(diào)整。(唯一的條件顯然是我們能夠計(jì)算出他的間隔時(shí)間)。
注意,對(duì)于這個(gè)函數(shù),我們選擇水平線,高度為1/QPS,因此這個(gè)函數(shù)的影響是不存在的。對(duì)于storedPermits將會(huì)完全等同于刷新一個(gè)新的(1/QPS是他的代價(jià))。我們將會(huì)在之后使用這個(gè)小訣竅。
如果我們采用一個(gè)低于這條水平線的函數(shù),這意味著我們減少了這個(gè)函數(shù)的區(qū)域,也就是時(shí)間。因此限流器就會(huì)在一段時(shí)間的利用不足后變快。另一方面,如果我們使用一個(gè)高于此水平線的函數(shù),這就意味著代表時(shí)間的區(qū)域增大,因此storedPermits將會(huì)比刷新一個(gè)新許可更耗時(shí),相應(yīng)地,限流器就會(huì)在一段時(shí)間的利用不足后變慢。
最后,考慮一個(gè)限流器以1permit/sec速率,當(dāng)前未被使用,有一個(gè)acquire(100)的請(qǐng)求到來(lái)。等待100sec才開(kāi)始執(zhí)行任務(wù)將會(huì)是很愚蠢的行為。為什么不作任何事情只等待呢?一個(gè)更好的方法是立刻允許請(qǐng)求(正如它是acquire(1)的請(qǐng)求一樣),并且按需要延緩此后的需求。在這個(gè)版本,我們?cè)试S立刻開(kāi)始執(zhí)行任務(wù),并且延緩100秒之后的請(qǐng)求,因此我們?cè)试S工作執(zhí)行而不是讓它空閑等待。
這里有很重要的因果關(guān)系。這意味著限流器不會(huì)記住最后請(qǐng)求的時(shí)刻,但它會(huì)記住下一個(gè)請(qǐng)求(預(yù)計(jì))時(shí)間。這也使我們能夠立即知道(見(jiàn)tryAcquire(timeout))指定時(shí)間timeout是否足夠?qū)⑽覀儙У较乱粋€(gè)調(diào)度的時(shí)間點(diǎn),因?yàn)槲覀兛偩S持那個(gè)。并且我們所指的“未被使用的限流器”也被這所定義:但我們觀察“下一個(gè)請(qǐng)求的期待到達(dá)時(shí)間”在過(guò)去,那么(now-past)的時(shí)間差將被看作RateLimiter未被正式使用時(shí)間。這也是被我們解釋為storedPermits的時(shí)間。(我們用空閑的時(shí)間產(chǎn)生的許可數(shù)來(lái)增加storedPermits)。所以,如果速率=1許可/sec,并且請(qǐng)求在之前那個(gè)請(qǐng)求后一秒后準(zhǔn)時(shí)到達(dá),那么storedPermits將永遠(yuǎn)不會(huì)增加。我們只會(huì)在當(dāng)晚于預(yù)期一秒時(shí)間的到達(dá),才會(huì)增加它。?
轉(zhuǎn)載于:https://www.cnblogs.com/jenkov/p/guava_ratelimiter.html
總結(jié)
以上是生活随笔為你收集整理的Guava之RateLimiter的设计的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: centos7下安装pip以及mysql
- 下一篇: Linux查看某个端口是否被占用