【優雅程式碼】17-guava限流原始碼解析
阿新 • • 發佈:2022-01-17
【優雅程式碼】17-guava限流原始碼解析
歡迎關注b站賬號/公眾號【六邊形戰士夏寧】,一個要把各項指標拉滿的男人。該文章已在github目錄收錄。
螢幕前的大帥比和大漂亮如果有幫助到你的話請順手點個贊、加個收藏這對我真的很重要。別下次一定了,都不關注上哪下次一定。
- 可直接執行的完整程式碼
- 上一篇guava布隆過濾與限流演算法原始碼解析
- 下一篇利用function實現list、tree互轉工具
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核心原始碼
- 構造方法
// 主流程方法,保留了部分說明註釋,這裡建立了一個計時器後面用於計算時間的 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); }
- 獲取令牌
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; } }