Guava(二)限流算法的使用

Guava中限流算法是通过RateLimiter来实现的。关于其设计的理解参见:https://www.cnblogs.com/krock/p/16348037.html

我们先来看下使用效果:

public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        Thread task = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + ": " + (System.currentTimeMillis() / 1000));
            }
        });
        // 创建一个限流器,参数就是令牌创建的速率  每秒1个
        RateLimiter limiter = RateLimiter.create(1);
        for (int i = 0; i <20 ; i++) {
//使用的时候,acquire(n) 表示从桶中拿到n个令牌才能继续往下走,如果桶中的令牌不够会阻塞直到桶中生成足够的令牌。 limiter.acquire(
1); executorService.submit(task); } }

控制台的内容就是:每秒输出一条信息

下面跟一下create方法和acquire方法

public static RateLimiter create(double permitsPerSecond) {
    /*
     * The default RateLimiter configuration can save the unused permits of up to one second. This
     * is to avoid unnecessary stalls in situations like this: A RateLimiter of 1qps, and 4 threads,
     * all calling acquire() at these moments:
     *
     * 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.
     */
// 第二个参数创建一个SleepingStopwatch的实例,用来记录这个限流器的开始时间和请求到达的时间
return create(permitsPerSecond, SleepingStopwatch.createFromSystemTimer()); }

 

 static RateLimiter create(double permitsPerSecond, SleepingStopwatch stopwatch) {
// 上面我们使用的限流器实例是SmoothBursty RateLimiter rateLimiter
= new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */); rateLimiter.setRate(permitsPerSecond); return rateLimiter; }

 

看下SmoothBursty是如何构建的:

SmoothBursty(SleepingStopwatch stopwatch, double maxBurstSeconds) {
      super(stopwatch);  // 给全局的stopwatch赋值
// 上面构建的参数为 1.0,流量突变的最大时间
this.maxBurstSeconds = maxBurstSeconds; }

 

setRate, 这个方法是在RateLimiter中的

public final void setRate(double permitsPerSecond) {
    checkArgument(
        permitsPerSecond > 0.0 && !Double.isNaN(permitsPerSecond), "rate must be positive");
    synchronized (mutex()) {  // 加了一把互斥锁
//这个方法需要子类重写的,SmoothBursty和SmoothWarmingUp 逻辑不一样 doSetRate(permitsPerSecond, stopwatch.readMicros()); } }

 

@Override
  final void doSetRate(double permitsPerSecond, long nowMicros) {
// 同步下数据,这里主要是初始化一些数据 resync(nowMicros);
// 计算图形中的stableInterval
double stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond; this.stableIntervalMicros = stableIntervalMicros; doSetRate(permitsPerSecond, stableIntervalMicros); }

 

void resync(long nowMicros) {
    // if nextFreeTicket is in the past, resync to now
    //  如果 nextFreeTicket 时间落后了,说明RateLimiter有一段时间没用了,需要同步下最新的时间
    if (nowMicros > nextFreeTicketMicros) {
      //  coolDownIntervalMicros  在SmoothBursty中就是两次请求的时间间隔,就是上面计算的stableIntervalMicros也可以认为生成一个令牌的时间间隔
      //  这个计算出来在落后的这段时间内又生成了多少个令牌
      double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();
      // 更新最新的令牌数量 当然不能超过最大的令牌数量
      storedPermits = min(maxPermits, storedPermits + newPermits);
      nextFreeTicketMicros = nowMicros;
    }
  }

 


 

具体使用的方法acquire

 public double acquire(int permits) {
     //获取这些令牌permits需要等待的时间
    long microsToWait = reserve(permits);
//    System.out.println("准备休眠的时间:"+ microsToWait);
    //如果等待时间大于0 会进行休眠
    stopwatch.sleepMicrosUninterruptibly(microsToWait);
// 返回休眠了多久
return 1.0 * microsToWait / SECONDS.toMicros(1L); }

 

final long reserve(int permits) {
    // 检查参数必须大于0
    checkPermits(permits);
    // 加了一把公共的锁
    synchronized (mutex()) {
      // 第二个参数是 从RateLimiter创建到现在经过多长时间
      return reserveAndGetWaitLength(permits, stopwatch.readMicros());
    }
  }
final long reserveAndGetWaitLength(int permits, long nowMicros) {
    // 当令牌不足够的时候,返回下次可以放行的时间
    long momentAvailable = reserveEarliestAvailable(permits, nowMicros);
    // 计算时间差,就是需要等待的时间,但是必须大于零
    return max(momentAvailable - nowMicros, 0);
  }
final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
    //如果RateLimiter长时间未使用,需要同步令牌桶中的令牌数量和 nextFreeTicketMicros
    resync(nowMicros);
    long returnValue = nextFreeTicketMicros;
    // 比较桶中存储的令牌 storedPermits 和想获取的令牌数量 取最小的那个
    // 就是得到本次请求可以消耗的令牌数量
    double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
    // 需要刷新的令牌数量
    // 两种情况: 1: 桶里的令牌数量足够,也就是 storedPermitsToSpend == requiredPermits 那这个 freshPermits == 0
    //           2:  桶里的令牌数量不够,也就是 storedPermitsToSpend == storedPermits < requiredPermits
    //                那 requiredPermits - storedPermitsToSpend > 0
    double freshPermits = requiredPermits - storedPermitsToSpend;
    // 计算本次请求获取requiredPermits个令牌需要等待的时间
    // + 后面的 (long) (freshPermits * stableIntervalMicros) 就是计算当桶中令牌不够的时候生成新的令牌需要的时间
    // + 前面的 storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)  就是计算从桶里取 storedPermitsToSpend
    //  个令牌需要花费的时间 就是前面类注释上提到的函数区间上做积分(SmoothWarmingUp 对于这个限流来说)
    // 对于SmoothBursty来说 从桶里取出令牌是不花时间的,直接返回了0,而对于SmoothWarmingUp来说预热就体现在这里,从桶里取出令牌是需要时间的。
    long waitMicros =storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
            + (long) (freshPermits * stableIntervalMicros);
    //计算 nextFreeTicketMicros,waitMicros 之和,需要等待多久才能得到需要的
    this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
    //减少桶中的令牌数量
    this.storedPermits -= storedPermitsToSpend;
    return returnValue;
  }
 void resync(long nowMicros) {
    // if nextFreeTicket is in the past, resync to now
    //  如果 nextFreeTicket 时间落后了,说明RateLimiter有一段时间没用了,需要同步下最新的时间
    if (nowMicros > nextFreeTicketMicros) {
      //  coolDownIntervalMicros  在SmoothBursty中就是两次请求的时间间隔,也可以认为生成一个令牌的时间间隔
      //  这个计算出来在落后的这段时间内又生成了多少个令牌
      double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();
      // 更新最新的令牌数量 当然不能超过最大的令牌数量
      storedPermits = min(maxPermits, storedPermits + newPermits);
      nextFreeTicketMicros = nowMicros;
    }
  }

 

在 reserveEarliestAvailable 中可以看到最新的 nextFreeTicketMicros 数据并没有返回出去,所以说这个限流影响的是下次请求,而不是当前请求。

在 waitMicros 这个变量计算的时候分了两部分来的,前面这部分是计算从令牌桶中取出令牌花费的时间,后面部分当桶中令牌数不够的时候需要新生成的时间。

对于前面部分从桶中取出令牌花费的时间,SmoothBursty中的逻辑是:

long storedPermitsToWaitTime(double storedPermits, double permitsToTake) {
      return 0L;
    }

 

而  SmoothWarmingUp 中的逻辑是:

long storedPermitsToWaitTime(double storedPermits, double permitsToTake) {
      // 可以判断目前桶中的令牌是否超过阈值 thresholdPermits
      double availablePermitsAboveThreshold = storedPermits - thresholdPermits;
      long micros = 0;
      // measuring the integral on the right part of the function (the climbing line)
      // 如果超过 thresholdPermits 阈值,则计算梯形的积分来计算时间
      if (availablePermitsAboveThreshold > 0.0) {
        // 看需要获取的令牌处在梯形部分的令牌有多少
        double permitsAboveThresholdToTake = min(availablePermitsAboveThreshold, permitsToTake);
        // TODO(cpovirk): Figure out a good name for this variable.
        // 就算梯形部分的面积(花费的时间)
        // 两次permitsToTime函数计算结果相加就是计算梯形的 上底+下底
        double length =
            permitsToTime(availablePermitsAboveThreshold)
                + permitsToTime(availablePermitsAboveThreshold - permitsAboveThresholdToTake);
        // 梯形的高就是 permitsAboveThresholdToTake  micros就是梯形的面积,就是消耗 permitsAboveThresholdToTake 个令牌的时间
        micros = (long) (permitsAboveThresholdToTake * length / 2.0);
        // 减去梯形部分的令牌,如果 permitsToTake大于0,那么还需要从矩形部分的令牌中获取
        permitsToTake -= permitsAboveThresholdToTake;
      }
      // measuring the integral on the left part of the function (the horizontal line)
      // 矩形部分的令牌,速率是固定的就是 stableIntervalMicros,乘上 permitsToTake 就是消耗的时间
      micros += (long) (stableIntervalMicros * permitsToTake);
      return micros;
    }

 

得到时间之后我们看下:  stopwatch.sleepMicrosUninterruptibly(microsToWait); 如何休眠的

public double acquire(int permits) {
     //需要等待的时间
    long microsToWait = reserve(permits);
//    System.out.println("准备休眠的时间:"+ microsToWait);
    //如果等待时间大于0 会进行休眠
    stopwatch.sleepMicrosUninterruptibly(microsToWait);
    return 1.0 * microsToWait / SECONDS.toMicros(1L);
  }

 

protected void sleepMicrosUninterruptibly(long micros) {
          if (micros > 0) {
            Uninterruptibles.sleepUninterruptibly(micros, MICROSECONDS);
          }
        }

 

public static void sleepUninterruptibly(long sleepFor, TimeUnit unit) {
    boolean interrupted = false;
    try {
      long remainingNanos = unit.toNanos(sleepFor);
      long end = System.nanoTime() + remainingNanos;
      while (true) {
        try {
          // TimeUnit.sleep() treats negative timeouts just like zero.
          NANOSECONDS.sleep(remainingNanos);
          return;
        } catch (InterruptedException e) {
          interrupted = true;
          remainingNanos = end - System.nanoTime();
        }
      }
    } finally {
      if (interrupted) {
        Thread.currentThread().interrupt();
      }
    }
  }

 

也是调用Thread中的休眠方法。

 

posted @ 2022-06-08 07:47  蒙恬括  阅读(676)  评论(0编辑  收藏  举报