RateLimter源碼解析

RateLimiter

類圖

圖片描述

RateLimiter:做爲抽象類提供一個限流器的基本的抽象方法。
SmoothRateLimiter:平滑限流器實現,提供了Ratelimiter中的抽象限流方法的平滑實現。
SmoothBursty:容許突發流量的平滑限流器的實現。
SmoothWarmingUp:平滑預熱限流器的實現。函數

實例

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));
        }
    }

執行結果
圖片描述ui

acquire方法返回結果表明獲取token所等待的時間。this

第一行0等待,剛建立限流器,還沒來得及聽任何token,此處存儲的token=0,可是無欠款因此預消費2個;
sleep 2秒,按照每秒2個的速度,先「還」了欠款,而後token直接恢復至max = 2;
第二行0,現有2個token,用一個,無需等待。
第三行0,現有1個token,用一個,無需等待。
第四行0,現有0個token,無欠款,直接借10個。
第五行4.999868,幫上一個還欠款,等待5秒直到還完欠款後,又借了2個。
重複第一行......spa

在使用RateLimiter.create(double)方法初始化限流器時,實際上默認使用的是SmoothBurstypwa

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.
    當前存儲的令牌數
 */
double storedPermits;

/**
 * The maximum number of stored permits.
 * 最大存儲令牌數
 */
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.
 * 添加令牌時間間隔
 */
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.
 * 下一次請求能夠獲取令牌的起始時間
 * 因爲RateLimiter容許預消費,上次請求預消費令牌後
 * 下次請求須要等待相應的時間到nextFreeTicketMicros時刻才能夠獲取令牌
 */
private long nextFreeTicketMicros = 0L; // could be either in the past or future

從acquire()函數開始線程

public double acquire() {
    return acquire(1);//默認取一個令牌
  }
public double acquire(int permits) {
    long microsToWait = reserve(permits);//從限流器中獲取指定的令牌,並返回須要等待的時間
    stopwatch.sleepMicrosUninterruptibly(microsToWait);//讓「鬧鐘」將當前線程中止睡眠指定時間
    return 1.0 * microsToWait / SECONDS.toMicros(1L);//返回等待的時間,單位是秒
  }
final long reserve(int permits) {
    checkPermits(permits);//參數校驗
    synchronized (mutex()) {//獲取鎖,多個請求到達,須要串行的獲取
      return reserveAndGetWaitLength(permits, stopwatch.readMicros());
    }
  }

先來看下加鎖的邏輯code

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;
  }

典型的雙重檢查單例
接着繼續探索獲取令牌的邏輯代碼orm

final long reserveAndGetWaitLength(int permits, long nowMicros) {
    long momentAvailable = reserveEarliestAvailable(permits, nowMicros);//獲取token並返回下個請求能夠來獲取token的時間
    return max(momentAvailable - nowMicros, 0);//計算等待時間
  }

關鍵函數一:
abstract long reserveEarliestAvailable(int permits, long nowMicros);
SmoothRateLimiter實現了它,是獲取token、消耗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;
  }
  1. 更新令牌桶中的token。
  2. 計算下次能得到令牌的時間
  3. 扣除本次所需令牌

storedPermitsToSpend表明須要從storedPermits扣除的token,若是storedPermits已經=0了,那麼不會扣除到負數
waitMicros表明這次預消費的令牌須要多少時間來恢復,最終將它加到nextFreeTicketMicros**
那麼SmoothBursty是怎麼實現預消費的呢?,讓咱們先看下更新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;
    }
  }

更新流程

  1. 若是當前時間大於freeTime,則進入更新操做。
  2. 將時間差除以令牌恢復間隔,計算出獲得恢復的令牌個數
  3. 更新令牌桶令牌的存儲數量和freeTime

SmoothBursty是怎麼實現預消費的呢?
其實,只要保證一點就能夠進行預消費,即無欠款,無欠款就表明當前時間大於等於nextFreeTime,SmoothBursty就是依靠此原理來處理突發的流量。

SmoothWarmingUp

先看下示例代碼

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));
        }
    }

運行效果

0.0
1.372264
1.117788
0.869905
0.620505
0.496059
0.495301
0.496027
0.495794

SmoothWarmingUp爲了預防忽然暴增的流量將系統壓垮,很貼心的增長了「預熱」。指定的warmupPeriod就是預熱時間,在「冷狀態」即沒有流量進入時,放入每一個token的時間不單單是1/permitsPerSecond,還要加上一個預熱時間,類註釋上的圖做了很好的解釋。

clipboard.png

SmoothWarmingUp在初始階段與SmoothBursty有點不一樣,SmoothWarmingUp初始storePermits = maxPermits。一直使用permits直至storePermits減小到thresholdPermits(setRate調用時計算)放入token的時間便穩定下來,到達了「熱狀態」,此時與SmoothBursty是如出一轍。可是若是在warmupPeriod時間內沒有流量進入,則再次會進入「冷狀態「。

在實現上SmoothWarmingUp與SmoothBursty基本相同,惟一不一樣僅僅只有兩個函數的實現上

  1. coolDownIntervalMicros()返回一個token的冷卻時間,SmoothWarmingUp註釋中有介紹,爲了保證在warmUpPeriod時間恰好能夠恢復maxPermits個token,所以SmoothWarmingUp此函數返回的是warmupPeriodMicros / maxPermits
  2. storedPermitsToWaitTime(double storedPermits, double permitsToTake)返回消耗permitsToTake個token所須要等待的時間,SmoothBursty則是直接返回0.

SmoothWarmingUp的註釋解釋的很到位,在預熱限流器中,計算token的等待時間就能夠轉化計算圖中的面積,你們能夠順着註釋推導一下。

總結

SmoothBursty:初始token爲0,容許預消費,放入token的時間固定爲1/permitsPerSecond.(一開始直接上)
SmoothWarmingUp:初始token爲MaxPermits,容許預消費,能夠指定預熱時間,在與預熱時間事後速率恢復平穩與SmoothBursty一致。(老司機有前戲)

SmoothWarmingUp像了改良版的SmoothBursty,有個預熱時間,系統能更加從容的應付流量的來襲,所以通常能夠優先使用SmoothWarmingUp。

相關文章
相關標籤/搜索