限流
高并發(fā)系統(tǒng)有三大利器:緩存 带饱、限流 毡代、降級(jí)。
對(duì)于限流的實(shí)現(xiàn)勺疼,有多種算法:計(jì)數(shù)器教寂,漏桶法,令牌桶法执庐。
計(jì)數(shù)器法無(wú)法應(yīng)對(duì)極短時(shí)間內(nèi)的過(guò)量請(qǐng)求酪耕,而漏桶法無(wú)法處理短時(shí)間內(nèi)的突發(fā)請(qǐng)求,而令牌桶法能夠解決上述兩個(gè)問(wèn)題轨淌。所以迂烁,一般常用的是令牌桶法。
這里的限流與 Spring Cloud 的 Hystrix 的限流差異比較大递鹉,前者是“保護(hù)下游”盟步,后者是為了“保護(hù)自我”。并且躏结,RateLimiter 關(guān)心的其實(shí)是“流量整形”却盘,將不規(guī)整流量在一定速度內(nèi)規(guī)整。
Guava RateLimiter
Guava類庫(kù)中提供了令牌痛限流器的工具類RateLimiter媳拴,下面黄橘,我們具體看下Guava中令牌桶的具體實(shí)現(xiàn)。
RateLimiter的使用:
- 首先通過(guò)RateLimiter.create(1);創(chuàng)建一個(gè)限流器禀挫,參數(shù)代表每秒生成的令牌數(shù)
- 通過(guò)limiter.acquire(i);來(lái)以阻塞的方式獲取令牌旬陡,
也可以通過(guò)tryAcquire(int permits, long timeout, TimeUnit unit)來(lái)設(shè)置等待超時(shí)時(shí)間的方式獲取令牌,如果超timeout為0语婴,則代表非阻塞描孟,獲取不到立即返回驶睦。
RateLimiter的實(shí)現(xiàn)
如何持續(xù)產(chǎn)生令牌?
接近現(xiàn)實(shí)生活的想法是匿醒,由一個(gè)單獨(dú)的線程定時(shí)向桶中放入token(其實(shí)就是對(duì)一個(gè)計(jì)數(shù)進(jìn)行增加)场航,但這樣做會(huì)無(wú)謂的浪費(fèi)處理器資源。
另一種解法則是延遲計(jì)算廉羔,它是在嘗試獲取令牌時(shí)溉痢,根據(jù)上次產(chǎn)生空閑令牌的時(shí)間點(diǎn)和令牌產(chǎn)生的速率來(lái)計(jì)算等待時(shí)間以及下次產(chǎn)生新的空閑令牌的時(shí)間點(diǎn)。
具體的代碼如下憋他,主要邏輯在reserveNextTicket中:
public void acquire(int permits) {
checkPermits(permits);
long microsToWait;
synchronized (mutex) {
microsToWait = reserveNextTicket(permits, readSafeMicros());
}
ticker.sleepMicrosUninterruptibly(microsToWait);
}
/**
* Reserves next ticket and returns the wait time that the caller must wait for.
*/
private long reserveNextTicket(double requiredPermits, long nowMicros) {
resync(nowMicros);
long microsToNextFreeTicket = nextFreeTicketMicros - nowMicros;
double storedPermitsToSpend = Math.min(requiredPermits, this.storedPermits);
double freshPermits = requiredPermits - storedPermitsToSpend;
long waitMicros = storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
+ (long) (freshPermits * stableIntervalMicros);
this.nextFreeTicketMicros = nextFreeTicketMicros + waitMicros;
this.storedPermits -= storedPermitsToSpend;
return microsToNextFreeTicket;
}
具體的講孩饼,每次aquire都會(huì)調(diào)用resync計(jì)算桶中的令牌數(shù)竹挡,每次aquire后都會(huì)計(jì)算下一個(gè)可用的token的產(chǎn)生時(shí)刻nextFreeTicketMicros,aquire時(shí)揪罕,會(huì)將nextFreeTicketMicros減去當(dāng)前時(shí)刻,得到的就是等待時(shí)間好啰,同時(shí)會(huì)更新存儲(chǔ)的token數(shù)目轩娶,以及nextFreeTicketMicros。
如上框往,是獲取令牌的主要邏輯。
兩種RateLimiter
Guava中的RateLimiter有兩種實(shí)現(xiàn):
- WarmingUp(令牌生成速度緩慢提升到穩(wěn)定值)
- Bursty(令牌生成速度恒定)
當(dāng)服務(wù)一段時(shí)間沒(méi)有被使用時(shí)搅窿,RateLimiter會(huì)積累一定數(shù)目的令牌,從服務(wù)的角度來(lái)看男应,這時(shí)有兩種情況:
(1)服務(wù)有大量的空閑資源,可供調(diào)用者使用娱仔;—— BurstyRateLimiter
(2)由于長(zhǎng)時(shí)間沒(méi)有使用沐飘,緩存等功能失效,啟動(dòng)時(shí)需要預(yù)熱操作耐朴;—— WarmingUpRateLimiter
預(yù)熱版本W(wǎng)armingUpRateLimiter的特別之處在于
(1)桶中令牌數(shù)目的算法
(2)下次新的可用令牌產(chǎn)生的時(shí)間的算法:
如下圖