RateLimiter源码分析

RatleLimiter源码分析

本文基于版本

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>30.0-jre</version>
</dependency>

先看下RateLimiter中的类图

在这里插入图片描述

RateLimiter中的成员变量

  /**
   * 主要用作计时器 SleepingStopwatch是rateLimiter中的一个内部类,创建时就启动了
   * {@link Stopwatch.createStarted()}
   */
  private final SleepingStopwatch stopwatch;

  private volatile @Nullable Object mutexDoNotUseDirectly;

但是 Object mutexDoNotUseDirectly 我就没明白为什么要这么设计? 请读者不吝赐教。

  private Object mutex() {
    Object mutex = mutexDoNotUseDirectly;
    if (mutex == null) {
      synchronized (this) {
        mutex = mutexDoNotUseDirectly;
        if (mutex == null) {
          mutexDoNotUseDirectly = mutex = new Object();
        }
      }
    }
    return mutex;
  }
  //用到的地方都是 这里防止并发到底有没有必要这么去设计? 这么设计有什么好处? 考虑点在哪儿?
  synchronized (mutex()) {
  }

SmoothRateLimiter的成员变量

 /** 当前存储的permits数量 */
  double storedPermits;
  /** 最大允许缓存的 permits 数量,也就是 storedPermits 能达到的最大值 */
  double maxPermits;
  /**
   * 每隔多少时间产生一个 permit,例如 1s- > 5permit 则200ms/1permit
   */
  double stableIntervalMicros;
  /**
   * 下一次可以获取 permits 的时间,这个时间是根据stopwatch.readMicros()来的,每次请求时都会刷新这个时间
   */
  private long nextFreeTicketMicros = 0L; 

SmoothBursty 分析

RateLimiter 的静态构造方法:

  public static RateLimiter create(double permitsPerSecond) {
     //在创建{@link RateLimiter}时就启动{@link SleepingStopwatch}
    return create(permitsPerSecond, SleepingStopwatch.createFromSystemTimer());
  }
  
  /*
   * maxBurstSeconds=1.0,最多会缓存1s的请求数到池中,有0 <= storedPermits <= maxPermits = 1*permitsPerSecond
   */
  static RateLimiter create(double permitsPerSecond, SleepingStopwatch stopwatch) {
    RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */);
    rateLimiter.setRate(permitsPerSecond);
    return rateLimiter;
  }

我们继续往后看 setRate() 方法,这个方法时调控permits的速率的:

	public final void setRate(double permitsPerSecond) {
	  checkArgument(
	      permitsPerSecond > 0.0 && !Double.isNaN(permitsPerSecond), "rate must be positive");
	   // 这个控制并发为何如此设计, 有哪位读者可否解答下!!!!!
	  synchronized (mutex()) {
	    doSetRate(permitsPerSecond, stopwatch.readMicros());
	  }
	}

	final void doSetRate(double permitsPerSecond, long nowMicros) {
	  // 这个方法需要看下
	  resync(nowMicros);
	  double stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;
	  this.stableIntervalMicros = stableIntervalMicros;
	  doSetRate(permitsPerSecond, stableIntervalMicros);
	}
	
	void resync(long nowMicros) {
	 /**
	  * 如果很长时间没有请求limiter.acquire(),这段时间会内生成了很多permits,需要调整storedPermits
	  * coolDownIntervalMicros()在SmoothBursty中就是stableIntervalMicros(产生一个permit需要的时间) 	
	  */
	  if (nowMicros > nextFreeTicketMicros) {
	    double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();
	    storedPermits = min(maxPermits, storedPermits + newPermits);
	    nextFreeTicketMicros = nowMicros;
	  }
	}

继续doSetRate()

  @Override
  void doSetRate(double permitsPerSecond, double stableIntervalMicros) {
    double oldMaxPermits = this.maxPermits;
    maxPermits = maxBurstSeconds * permitsPerSecond;
    if (oldMaxPermits == Double.POSITIVE_INFINITY) {
      // Double.POSITIVE_INFINITY(1/0)无穷大
      storedPermits = maxPermits;
    } else {
      // 这个地方可能需要缩放,例如创建RateLimiter后再手动调用RateLimiter.setRate(int x)
      storedPermits =(oldMaxPermits == 0.0) ? 0.0 : storedPermits * maxPermits/oldMaxPermits;
    }
  }

RateLimiter创建时的源码就到这儿,接下来,我们来分析 acquire() 方法:

  public boolean tryAcquire() {
    return tryAcquire(1, 0, MICROSECONDS);
  }
  
  public boolean tryAcquire(int permits, long timeout, TimeUnit unit) {
    long timeoutMicros = max(unit.toMicros(timeout), 0);
    // 校验permits>0
    checkPermits(permits);
    long microsToWait;
    synchronized (mutex()) {
      long nowMicros = stopwatch.readMicros();
      if (!canAcquire(nowMicros, timeoutMicros)) {
        return false;
      } else {
        microsToWait = reserveAndGetWaitLength(permits, nowMicros);
      }
    }
    // SmoothBursty 下 microsToWait=0
    stopwatch.sleepMicrosUninterruptibly(microsToWait);
    return true;
  }

 /**
   * queryEarliestAvailable(nowMicros)返回 nextFreeTicketMicros
   * 如果有预占的情况时nextFreeTicketMicros = nowMicros+(生成预占数量的时间)
   */
  private boolean canAcquire(long nowMicros, long timeoutMicros) {
    return queryEarliestAvailable(nowMicros) - timeoutMicros <= nowMicros;
  }

我们看下reserveAndGetWaitLength()方法:

	/**
	 * Reserves next ticket and returns the wait time that the caller must wait for.
	 * @return the required wait time, never negative
	 */
	final long reserveAndGetWaitLength(int permits, long nowMicros) {
	  long momentAvailable = reserveEarliestAvailable(permits, nowMicros);
	  return max(momentAvailable - nowMicros, 0);
	}

	final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
		// 跟上面分析一样重新调整nextFreeTicketMicros 和 storedPermits,每次请求时都需调整
	   resync(nowMicros);
	   long returnValue = nextFreeTicketMicros;
	   double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
	   // 缓存的permits不足数量,用来计算预占时间
	   double freshPermits = requiredPermits - storedPermitsToSpend;
	   // SmoothBursty中storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)=0,预热的时间
	   long waitMicros =
	       storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
	       // SmoothWarmingUp模式中超过的permits也相当于匀速生成
	           + (long) (freshPermits * stableIntervalMicros);
		// 重新计算需加上预支的时间
	   this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
	   this.storedPermits -= storedPermitsToSpend;
	   // 返回上次计算的值
	   return returnValue;
	 }

SmoothBursty分析完了,代码不多,逻辑也挺简单,需要注意的是在RateLimiter.create(5)
创建时storedPermits=0maxPermits=5.0,只有在第一次rateLimiter.tryAcquire()时才会根据时间差计算出storedPermits的值(见resync(long nowMicros)) 。

SmoothWarmingUp 分析

SmoothWarmingUp
适用于资源需要预热的场景。比如我们的某个接口业务,需要使用到数据库连接,由于连接需要预热才能进入到最佳状态,如果我们的系统长时间处于低负载或零负载状态(当然,应用刚启动也是一样的),连接池中的连接慢慢释放掉了,此时我们认为连接池是冷的。

先看一张图:

在这里插入图片描述

再结合 doSetRate() 源码看下计算公式


   void doSetRate(double permitsPerSecond, double stableIntervalMicros) {
     double oldMaxPermits = maxPermits;
     // coldFactor = 3 硬编码
     double coldIntervalMicros = stableIntervalMicros * coldFactor;
	 /**
	 * 至于为什么是 1/2 我是没搞明白, 有谁知道的,请赐教? 源码中有如下解释:
	 * <li>The time to go from thresholdPermits to 0 is equal to the integral of the function
	 *   between 0 and thresholdPermits. This is thresholdPermits * stableIntervals. By (5) it is
	 *   also equal to warmupPeriod/2. Therefore
	 *    <blockquote>
	 *     thresholdPermits = 0.5 * warmupPeriod / stableInterval
	 *    </blockquote>
	 * <li>
	 * 至于为什么从thresholdPermits到0的时间等于从maxPermits到thresholdPermits的时间,我没明白!!
	 */
     thresholdPermits = 0.5 * warmupPeriodMicros / stableIntervalMicros;
     maxPermits =
         thresholdPermits + 2.0 * warmupPeriodMicros / (stableIntervalMicros + coldIntervalMicros);
      //计算梯形的斜率
     slope = (coldIntervalMicros - stableIntervalMicros) / (maxPermits - thresholdPermits);
     if (oldMaxPermits == Double.POSITIVE_INFINITY) {
       // if we don't special-case this, we would get storedPermits == NaN, below
       storedPermits = 0.0;
     } else {
       // 初始化时SmoothBursty中storedPermits=0,而SmoothWarmingUp中storedPermits= maxPermits 
       storedPermits = (oldMaxPermits == 0.0)? maxPermits : storedPermits * maxPermits / oldMaxPermits;
     }
   }

RateLimiter 的静态构造方法:

  public static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit) {
    checkArgument(warmupPeriod >= 0, "warmupPeriod must not be negative: %s", warmupPeriod);
    return create(permitsPerSecond, warmupPeriod, unit, 3.0, SleepingStopwatch.createFromSystemTimer());
  }

  static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit,
      double coldFactor, SleepingStopwatch stopwatch) {
    // 可以看到coldFactor的硬编码为3 
    RateLimiter rateLimiter = new SmoothWarmingUp(stopwatch, warmupPeriod, unit, coldFactor);
    rateLimiter.setRate(permitsPerSecond);
    return rateLimiter;
  }

SmoothBursty 中基本一致,区别就在
构造函数以及上面的doSetRate()源码,下面分析下tryAcquire()这个核心方法,先看下和SmoothBursty
resync()的区别

  void resync(long nowMicros) {
    // if nextFreeTicket is in the past, resync to now
    if (nowMicros > nextFreeTicketMicros) {
      // 和SmoothBursty 的区别在 coolDownIntervalMicros()
      double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();
      storedPermits = min(maxPermits, storedPermits + newPermits);
      nextFreeTicketMicros = nowMicros;
    }
  }

  //  预热时间/最大permits
  double coolDownIntervalMicros() {
      return warmupPeriodMicros / maxPermits;
    }

看下和SmoothBurstyreserveEarliestAvailable()的区别

  final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
    resync(nowMicros);
    long returnValue = nextFreeTicketMicros;
    double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
    double freshPermits = requiredPermits - storedPermitsToSpend;
    long waitMicros =
    	// 和SmoothBursty的区别在 storedPermitsToWaitTime(),SmoothBursty 中为0
        storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
            + (long) (freshPermits * stableIntervalMicros);

    this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
    this.storedPermits -= storedPermitsToSpend;
    return returnValue;
  }
  
  /**
   * permitsToTake 可允许获取的permits数量
   * min(requiredPermits, this.storedPermits)
  */
  long storedPermitsToWaitTime(double storedPermits, double permitsToTake) {
  	// 超过thresholdPermits部分
    double availablePermitsAboveThreshold = storedPermits - thresholdPermits;
    long micros = 0;
    if (availablePermitsAboveThreshold > 0.0) {
      // permitsToTake 可获取的permits min(storedPermits,requiredPermits)
      double permitsAboveThresholdToTake = min(availablePermitsAboveThreshold, permitsToTake);
      double length =
          permitsToTime(availablePermitsAboveThreshold)
              + permitsToTime(availablePermitsAboveThreshold - permitsAboveThresholdToTake);
      micros = (long) (permitsAboveThresholdToTake * length / 2.0);
      permitsToTake -= permitsAboveThresholdToTake;
    } 
    micros += (long) (stableIntervalMicros * permitsToTake);
    return micros;
  }
  
  // 超过梯形的x值计算y值
  private double permitsToTime(double permits) {
	 return stableIntervalMicros + permits * slope;
  }

贴个图,可以仔细体会下

在这里插入图片描述

源码到这儿就分析结束了,SmoothWarmingUp要复杂不少,代码不重要,主要是要读懂guava这样设计的思想。
有时间的话,分析比较下sentinelgateway中的限流。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值