Guava中的RateLimiter實現令牌桶的技巧

Guava中的RateLimiter實現令牌桶的技巧

看一下網絡上面找的一張圖:
在這裏插入圖片描述

理解起來不難,但是如何用代碼來實現呢,借鑑Guava中的RateLimiter源碼,我們來看一下核心部分:
一些參數說明

/**
 * 添加令牌時間間隔
 */
double stableIntervalMicros;

/**
 * 下一次請求可以獲取令牌的起始時間
 * 由於RateLimiter允許預消費,上次請求預消費令牌後
 * 下次請求需要等待相應的時間到nextFreeTicketMicros時刻纔可以獲取令牌
 */
private long nextFreeTicketMicros = 0L; // could be either in the past or future

nextFreeTicketMicros 屬性就是說,會一直睡眠到nextFreeTicketMicros時間到了才返回
創建部分:

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類,也就是穩定生成令牌的類。

核心函數:

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

發現原來RateLimiter不是啓動一個線程,不斷往桶裏加令牌,而是調用的時候才往裏面加,其實現思路爲,若當前時間晚於nextFreeTicketMicros,則計算該段時間內可以生成多少令牌。

final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
  resync(nowMicros);
  long returnValue = nextFreeTicketMicros; // 返回的是上次計算的nextFreeTicketMicros
  double storedPermitsToSpend = min(requiredPermits, this.storedPermits); // 可以消費的令牌數
  double freshPermits = requiredPermits - storedPermitsToSpend; // 還需要的令牌數
  long waitMicros =
      storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
          + (long) (freshPermits * stableIntervalMicros); // 根據freshPermits計算需要等待的時間

  this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros); // 本次計算的nextFreeTicketMicros不返回
  this.storedPermits -= storedPermitsToSpend;
  return returnValue;
}

計算可以生成的令牌數是否比需要的令牌數多

public double acquire(int permits) {
    long microsToWait = reserve(permits);
    stopwatch.sleepMicrosUninterruptibly(microsToWait);
    return 1.0 * microsToWait / SECONDS.toMicros(1L);
  }

如果需要的令牌數比可以生成的令牌數多,則會睡眠一段時間,這段時間在上面的代碼中已經計算出來了。反之,則直接返回。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章