轉載http://www.cnblogs.com/LBSer/p/4083131.html
一、問題描述
某天A君突然發現自己的接口請求量突然漲到之前的10倍,沒多久該接口幾乎不可使用,並引發連鎖反應導致整個系統崩潰。如何應對這種情況呢?生活給了我們答案:比如老式電閘都安裝了保險絲,一旦有人使用超大功率的設備,保險絲就會燒斷以保護各個電器不被強電流給燒壞。同理我們的接口也需要安裝上“保險絲”,以防止非預期的請求對系統壓力過大而引起的系統癱瘓,當流量過大時,可以採取拒絕或者引流等機制。
二、常用的限流算法
常用的限流算法有兩種:漏桶算法和令牌桶算法。
漏桶算法思路很簡單,水(請求)先進入到漏桶裏,漏桶以一定的速度出水,當水流入速度過大會直接溢出,可以看出漏桶算法能強行限制數據的傳輸速率。
圖1 漏桶算法示意圖
對於很多應用場景來說,除了要求能夠限制數據的平均傳輸速率外,還要求允許某種程度的突發傳輸。這時候漏桶算法可能就不合適了,令牌桶算法更爲適合。如圖2所示,令牌桶算法的原理是系統會以一個恆定的速度往桶裏放入令牌,而如果請求需要被處理,則需要先從桶裏獲取一個令牌,當桶裏沒有令牌可取時,則拒絕服務。
圖2 令牌桶算法示意圖
三、限流工具類RateLimiter
Google開源工具包Guava提供了限流工具類RateLimiter,該類基於令牌桶算法來完成限流,非常易於使用。RateLimiter類的接口描述請參考:RateLimiter接口描述,具體使用請參考:RateLimiter使用實踐。
public double acquire() {
return acquire(1);
}
public double acquire(int permits) {
checkPermits(permits); //檢查參數是否合法(是否大於0)
long microsToWait;
synchronized (mutex) { //應對併發情況需要同步
microsToWait = reserveNextTicket(permits, readSafeMicros()); //獲得需要等待的時間
}
ticker.sleepMicrosUninterruptibly(microsToWait); //等待,當未達到限制時,microsToWait爲0
return 1.0 * microsToWait / TimeUnit.SECONDS.toMicros(1L);
}
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;
}
private void resync(long nowMicros) {
// if nextFreeTicket is in the past, resync to now
if (nowMicros > nextFreeTicketMicros) {
storedPermits = Math.min(maxPermits,
storedPermits + (nowMicros - nextFreeTicketMicros) / stableIntervalMicros);
nextFreeTicketMicros = nowMicros;
}
}