互聯網應對高峯流量控制- 漏桶算法和令牌桶算法(滴滴面試)

天A君忽然發現本身的接口請求量忽然漲到以前的10倍,沒多久該接口幾乎不可以使用,並引起連鎖反應致使整個系統崩潰。如何應對這種狀況呢?生活給了咱們答案:好比老式電閘都安裝了保險絲,一旦有人使用超大功率的設備,保險絲就會燒斷以保護各個電器不被強電流給燒壞。同理咱們的接口也須要安裝上「保險絲」,以防止非預期的請求對系統壓力過大而引發的系統癱瘓,當流量過大時,能夠採起拒絕或者引流等機制。 html

2、經常使用的限流算法

      經常使用的限流算法有兩種:漏桶算法和令牌桶算法java

一、漏桶算法git

      漏桶算法思路很簡單,水(請求)先進入到漏桶裏,漏桶以必定的速度出水,當水流入速度過大會直接溢出,能夠看出漏桶算法能強行限制數據的傳輸速率。算法

圖1 漏桶算法示意圖網絡

在某些狀況下,漏桶算法不可以有效地使用網絡資源。由於漏桶的漏出速率是固定的參數,因此即便網絡中不存在資源衝突(沒有發生擁塞),漏桶算法也不能使某一個單獨的流突發到端口速率。所以,漏桶算法對於存在突發特性的流量來講缺少效率。而令牌桶算法則可以知足這些具備突發特性的流量。一般,漏桶算法與令牌桶算法能夠結合起來爲網絡流量提供更大的控制。併發

二、令牌桶算法:工具

      對於不少應用場景來講,除了要求可以限制數據的平均傳輸速率外,還要求容許某種程度的突發傳輸。這時候漏桶算法可能就不合適了,令牌桶算法更爲適合。如圖2所示,令牌桶算法的原理是系統會以一個恆定的速度往桶裏放入令牌,而若是請求須要被處理,則須要先從桶裏獲取一個令牌,當桶裏沒有令牌可取時,則拒絕服務。ui

圖2 令牌桶算法示意圖this

  令牌桶算法的基本過程以下:google

  假如用戶配置的平均發送速率爲r,則每隔1/r秒一個令牌被加入到桶中;

  假設桶最多能夠存發b個令牌。若是令牌到達時令牌桶已經滿了,那麼這個令牌會被丟棄;

  當一個n個字節的數據包到達時,就從令牌桶中刪除n個令牌,而且數據包被髮送到網絡;

  若是令牌桶中少於n個令牌,那麼不會刪除令牌,而且認爲這個數據包在流量限制以外;

  算法容許最長b個字節的突發,但從長期運行結果看,數據包的速率被限制成常量r。對於在流量限制外的數據包能夠以不一樣的方式處理:

  它們能夠被丟棄;

  它們能夠排放在隊列中以便當令牌桶中累積了足夠多的令牌時再傳輸;

  它們能夠繼續發送,但須要作特殊標記,網絡過載的時候將這些特殊標記的包丟棄。

 

2、兩種算法的區別

二者主要區別在於「漏桶算法」可以強行限制數據的傳輸速率,而「令牌桶算法」在可以限制數據的平均傳輸速率外,還容許某種程度的突發傳輸。在「令牌桶算法」中,只要令牌桶中存在令牌,那麼就容許突發地傳輸數據直到達到用戶配置的門限,因此它適合於具備突發特性的流量。

 

 

3、限流工具類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;
        }
    }

參考:漏桶算法和令牌桶算法

相關文章
相關標籤/搜索