1. 程式人生 > 其它 >【優雅程式碼】17-guava限流原始碼解析

【優雅程式碼】17-guava限流原始碼解析

【優雅程式碼】17-guava限流原始碼解析

歡迎關注b站賬號/公眾號【六邊形戰士夏寧】,一個要把各項指標拉滿的男人。該文章已在github目錄收錄。
螢幕前的大帥比大漂亮如果有幫助到你的話請順手點個贊、加個收藏這對我真的很重要。別下次一定了,都不關注上哪下次一定。

1.背景

承接前一篇章的guava精選方法

2.限流

這部分和其它限流演算法的令牌桶演算法基本一致

2.1使用

@SneakyThrows
public static void rate() {
    // 這裡直接設定的就QPS(每秒查詢率)
    RateLimiter rateLimiter = RateLimiter.create(1);
    while (true){
        System.out.println(rateLimiter.tryAcquire());
        Thread.sleep(300);
    }
}
true
false
false
false
true

2.2核心原始碼

  1. 構造方法
// 主流程方法,保留了部分說明註釋,這裡建立了一個計時器後面用於計算時間的
public static RateLimiter create(double permitsPerSecond) {
    /*
     *
     * T0 at 0 seconds
     * T1 at 1.05 seconds
     * T2 at 2 seconds
     * T3 at 3 seconds
     *
     * Due to the slight delay of T1, T2 would have to sleep till 2.05 seconds, and T3 would also
     * have to sleep till 3.05 seconds.
     */
    return create(permitsPerSecond, SleepingStopwatch.createFromSystemTimer());
  }
// 繼續主流程往下走,然後預設構造了平均的令牌桶限流策略
static RateLimiter create(double permitsPerSecond, SleepingStopwatch stopwatch) {
    RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */);
    // 該方法標記為主流程方法2,設定速率,非常重要的方法
    rateLimiter.setRate(permitsPerSecond);
    return rateLimiter;
  }
// 該方法標記為主流程方法2
public final void setRate(double permitsPerSecond) {
    synchronized (mutex()) {
    	// 標記為方法主流程方法3,後面那個引數就是獲取當前時間的
      doSetRate(permitsPerSecond, stopwatch.readMicros());
    }
  }
// 該標記為方法主流程方法3,注意會有多個doSetRate,但是傳參不一樣
final void doSetRate(double permitsPerSecond, long nowMicros) {
	// 用於重新整理當前令牌數,在構造方法不重要,獲取令牌時會再次呼叫
    resync(nowMicros);
    // ****中間這個賦值很重要***,其它地方會在呼叫這個結果,這裡本身傳入的是QPS
    // 即原先為每秒可以獲取X個令牌,通過下列公式獲得,每微妙可獲得的令牌數,也可以從字面意思理解,獲得令牌的微秒間隔
    double stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;
    this.stableIntervalMicros = stableIntervalMicros;
    // 下面這個方法不重要
    doSetRate(permitsPerSecond, stableIntervalMicros);
  }
  1. 獲取令牌
public boolean tryAcquire() {
	// 直接不等待,走快速失敗的邏輯
    return tryAcquire(1, 0, MICROSECONDS);
  }

public boolean tryAcquire(int permits, long timeout, TimeUnit unit) {
    long timeoutMicros = max(unit.toMicros(timeout), 0);
    checkPermits(permits);
    long microsToWait;
    // 此處併發控制,每次只進入一個
    synchronized (mutex()) {
    	// 獲取當前時間
      long nowMicros = stopwatch.readMicros();
      	// 計算有沒有可用令牌,根據nextFreeTicketMicros
      if (!canAcquire(nowMicros, timeoutMicros)) {
        return false;
      } else {
      	// 獲取令牌並返回等待時間為0,進入該方法,主流程2
        microsToWait = reserveAndGetWaitLength(permits, nowMicros);
      }
    }
    stopwatch.sleepMicrosUninterruptibly(microsToWait);
    return true;
  }
 // 主流程2從上一步進入
final long reserveAndGetWaitLength(int permits, long nowMicros) {
	// 進入該方法,主流程3
    long momentAvailable = reserveEarliestAvailable(permits, nowMicros);
    // 因為now就是最大的,所以max是0
    return max(momentAvailable - nowMicros, 0);
  }
 // 主流程3從上一步進入。這裡涉及令牌桶的核心演算法
@Override
  final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
  	// 主流程4
    resync(nowMicros);
    long returnValue = nextFreeTicketMicros;
    // 返回要消耗掉的令牌數,這裡需要先看主流程4的邏輯,如果夠消耗則返回1,不夠消耗只有零點幾
    double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
    // 這裡有兩種情況,如果令牌有餘額,則1-1=0,如果令牌不足則獲得一個小於1的數
    double freshPermits = requiredPermits - storedPermitsToSpend;
    // 這裡左邊固定是0,就不貼原始碼了
    // 根據上面的情況,這裡有可能是0即有餘令牌,那麼再往下走兩行,下次重新整理時間則不動。如果小於1則令牌不夠,將差額和間隔進行相乘獲得還需要下個令牌的時間
    long waitMicros =
        storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
            + (long) (freshPermits * stableIntervalMicros);
     // 這裡賦值下次的時間,其實就是把兩個引數加起來,然後就得到了下次的時間,這也是為什麼示例中的2.05的下次就是3.05
    this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
    // 這裡減掉消耗掉的令牌數
    this.storedPermits -= storedPermitsToSpend;
    return returnValue;
  }

// 主流程4
 void resync(long nowMicros) {
    // if nextFreeTicket is in the past, resync to now
    // 如果當前時間大於下次獲取時間則進入,正常情況是一定大於的,否則就沒有多餘的令牌可以發
    if (nowMicros > nextFreeTicketMicros) {
    	// coolDownIntervalMicros()這個就是返回stableIntervalMicros
    	// 前面存的這玩意兒現在再除回去進行還原,即可以獲得的令牌數,注意這裡是可以返回零點幾的
      double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();
      // 相加得到現在的令牌數,根據上一行,這裡通常是幾點幾
      storedPermits = min(maxPermits, storedPermits + newPermits);
      // 標記獲取令牌時間
      nextFreeTicketMicros = nowMicros;
    }
  }