sentinel

Guava RateLimiter使用和源码分析

2019-10-23  本文已影响0人  lesline

使用

示例

   RateLimiter limiter = RateLimiter.create(5); // 这里的1表示每秒允许处理的量为1个
    for (int i = 1; i <= 10; i++) { 
        limiter.acquire();// 请求RateLimiter, 超过permits会被阻塞
    }
double waitTime=rateLimiter.acquire(1);//acquire方法传入的是需要的令牌个数,当令牌不足时会进行等待,该方法返回的是等待的时间
boolean isAcquire = limiter.tryAcquire(); //立刻失败
boolean isAcquire = limiter.tryAcquire(100, TimeUnit.MILLISECONDS);//等待一段时间后返回失败

需要注意的是,当令牌不足时,acquire方法并不会阻塞本次调用,而是会算在下次调用的头上
比如第一次调用时,令牌桶中并没有令牌,但是第一次调用也没有阻塞,而是在第二次调用的时候阻塞了1秒。
也就是说,每次调用欠的令牌(如果桶中令牌不足)都是让下一次调用买单

https://blog.csdn.net/fanrenxiang/article/details/80949079
Quick Guide to the Guava RateLimiter | Baeldung

原理

Guava的RateLimiter提供了令牌桶算法实现:平滑突发限流(SmoothBursty)和平滑预热限流(SmoothWarmingUp)实现。
[image:9A478AC0-7A3D-4615-8C13-414D39F053C5-35974-0002F48FC4E6F157/153F211F-1D78-4444-B6C6-8A0D527AA49B.png]

以SmoothBursty 为例,SmoothBursty中的几个关键字段:

// 桶中最多存放多少秒的令牌数
final double maxBurstSeconds;
//桶中的令牌个数
double storedPermits;
//桶中最多能存放多少个令牌,=maxBurstSeconds*每秒生成令牌个数
double maxPermits;
//加入令牌的平均间隔,单位为微秒,如果加入令牌速度为每秒5个,则该值为200ms
double stableIntervalMicros;
//下一个请求需要等待的时间
private long nextFreeTicketMicros = 0L;

获取令牌acquire函数如下所示,它会调用reserve函数计算获取目标令牌数所需等待的时间,然后使用SleepStopwatch进行休眠,最后返回等待时间。

public double acquire(int permits) {
    // 计算获取令牌所需等待的时间
  long microsToWait = reserve(permits);
    // 进行线程sleep
  stopwatch.sleepMicrosUninterruptibly(microsToWait);
  return 1.0 * microsToWait / SECONDS.toMicros(1L);
}
final long reserve(int permits) {
  checkPermits(permits);
  // 由于涉及并发操作,所以使用synchronized进行并发操作
  synchronized (mutex()) {
    return reserveAndGetWaitLength(permits, stopwatch.readMicros());
  }
}
final long reserveAndGetWaitLength(int permits, long nowMicros) {
    // 计算从当前时间开始,能够获取到目标数量令牌时的时间
    long momentAvailable = reserveEarliestAvailable(permits, nowMicros);
    // 两个时间相减,获得需要等待的时间
    return max(momentAvailable - nowMicros, 0);
}

可以看出主要逻辑都在reserveEarliestAvailable方法中;
reserveEarliestAvailable是刷新令牌数和下次获取令牌时间nextFreeTicketMicros的关键函数。
它有三个步骤:

  1. 一是调用resync函数增加令牌数,
  2. 二是计算预支付令牌所需额外等待的时间,
  3. 三是更新下次获取令牌时间nextFreeTicketMicros和存储令牌数storedPermits。
    这里涉及RateLimiter的一个特性,也就是可以预先支付令牌,并且所需等待的时间在下次获取令牌时再实际执行。详细的代码逻辑的解释请看注释。
@Override
final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
  // 刷新令牌数,相当于每次acquire时在根据时间进行令牌的刷新
  resync(nowMicros);
  long returnValue = nextFreeTicketMicros;
    // 目前即可得到的令牌数=min(请求目标令牌数,当前已有的令牌数)
  double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
    // 需要预先支付的令牌=目标令牌数-目前即可得到的令牌数
  double freshPermits = requiredPermits - storedPermitsToSpend;
  //watiMicros=预先支付的令牌所需等待的时间=需要预先支付的令牌*加入令牌的平均间隔
  long waitMicros =
      storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
          + (long) (freshPermits * stableIntervalMicros);
  // 更新nextFreeTicketMicros,本次预先支付的令牌所需等待的时间让下一次请求来实际等待。
  this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
  // 更新令牌数,最低数量为0
  this.storedPermits -= storedPermitsToSpend;
  // 返回旧的nextFreeTicketMicros数值,无需为预支付的令牌多加等待时间。
  return returnValue;
}
//storedPermitsToWaitTime在SmoothWarmingUp和SmoothBuresty的实现不同,用于实现预热缓冲期 
//SmoothBuresty 实现直接返回0
long storedPermitsToWaitTime(double storedPermits, double permitsToTake) {
    return 0L;
}
//刷新令牌数storedPermits和下次请求需要等待的时间nextFreeTicketMicros
void resync(long nowMicros) {
  // if nextFreeTicket is in the past, resync to now
  if (nowMicros > nextFreeTicketMicros) {
    //获取新增令牌数=(当前时间-下次请求需要等待的时间)/加入令牌的平均间隔
    double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();
    storedPermits = min(maxPermits, storedPermits + newPermits);
    nextFreeTicketMicros = nowMicros;
  }
}
double coolDownIntervalMicros() {
    return stableIntervalMicros;
}

其中 ::double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();::
就是新增令牌的核心逻辑。当前时间大于nextFreeTicketMicros时进行刷新,否则直接返回。

下面我们举个例子,让大家更好的理解resync和reserveEarliestAvailable函数的逻辑。
比如RateLimiter的stableIntervalMicros为500,也就是1秒发两个令牌,storedPermits为0,
nextFreeTicketMicros为155391849 5748。
线程一acquire(2),当前时间为155391849 6248,首先resync函数计算,(155391849 6248 - 155391849 5748)/500 = 1,所以当前可获取令牌数为1,但是由于可以预支付,所以
nextFreeTicketMicros= nextFreeTicketMicro + 1 * 500 = 155391849 6748。线程一无需等待。
线程二也来acquire(2),首先resync函数发现当前时间早于nextFreeTicketMicros,所以无法增加令牌数,所以需要预支付2个令牌, nextFreeTicketMicros= nextFreeTicketMicro + 2 * 500 = 155391849 7748。
线程二需要等待155391849 6748时刻,也就是线程一获取时计算的nextFreeTicketMicros时刻。
同样的,线程三获取令牌时也需要等待到线程二计算的nextFreeTicketMicros时刻。

来谈谈限流-RateLimiter源码分析 - 简书
使用Guava RateLimiter限流以及源码解析 - 简书

上一篇下一篇

猜你喜欢

热点阅读