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

看下SmoothBursty是如何构建的:

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

setRate, 这个方法是在RateLimiter中的

@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中的休眠方法。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

姑苏冷

您的打赏是对原创文章最大的鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值