Bootstrap

RateLimiter google限流组件试析(SmoothBursty/SmoothWarmingUp)

RateLimiter google限流组件试析(SmoothBursty/SmoothWarmingUp)


RateLimiter是guava包里的限流组件,位于com.google.common.util.concurrent.RateLimiter包下。

RateLimiter的使用:

RateLimiter limiter = RateLimiter.create(8);

在需要限流的地方只需:limiter.acquire();


看一下相关源码

SmoothBursty的创建

RateLimiter.create(8)对应的构造方法是RateLimiter create(double permitsPerSecond)

内部只有一行代码:

return create(SleepingStopwatch.createFromSystemTimer(), permitsPerSecond);

再看看这个内部的create方法:

    RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */);
    rateLimiter.setRate(permitsPerSecond);
    return rateLimiter;

SleepingStopwatch是RateLimiter内部的一个抽象类,用来抽象一个“可睡眠”的秒表类型。其内部有两个抽象方法:

    protected abstract long readMicros();
    protected abstract void sleepMicrosUninterruptibly(long micros);

createFromSystemTimer方法通过内置的System.nanoTime方法来获取时间戳,进而获取读秒数。

可见,一个是读秒的方法,一个是让线程睡眠的方法,二者均采用微秒作为单位。

    public static final SleepingStopwatch createFromSystemTimer() {
      return new SleepingStopwatch() {
        final Stopwatch stopwatch = Stopwatch.createStarted();

        @Override
        protected long readMicros() {
          return stopwatch.elapsed(MICROSECONDS);
        }

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

回过头来看RateLimiter的create方法

static RateLimiter create(SleepingStopwatch stopwatch, double permitsPerSecond) {
  RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */);
  rateLimiter.setRate(permitsPerSecond);
  return rateLimiter;
}

可以看到,返回值实际类型是SmoothBursty。涉及到的几个类的继承关系是这样的:

在这里插入图片描述

顶层父类RateLimiter中有一个mutex方法,用来获取互斥锁:

private volatile Object mutexDoNotUseDirectly;
//...
private Object mutex() {
    Object mutex = mutexDoNotUseDirectly;
    if (mutex == null) {
      synchronized (this) {
        mutex = mutexDoNotUseDirectly;
        if (mutex == null) {
          mutexDoNotUseDirectly = mutex = new Object();
        }
      }
    }
    return mutex;
  }

典型的双重校验单例模式,而且单例对象用volatile修饰了。

回过头来看RateLimiter.create方法:

static RateLimiter create(SleepingStopwatch stopwatch, double permitsPerSecond) {
  RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */);
  rateLimiter.setRate(permitsPerSecond);
  return rateLimiter;
}

第一行创建了SmoothBursty的实例,这个构造方法就是单纯的赋值操作。

第二行设置了每秒的允许的请求数,也就是调用create方法的入参,调用的是RateLimiter.setRate方法。

  public final void setRate(double permitsPerSecond) {
    checkArgument(
        permitsPerSecond > 0.0 && !Double.isNaN(permitsPerSecond), "rate must be positive");
    synchronized (mutex()) {
      doSetRate(permitsPerSecond, stopwatch.readMicros());
    }
  }

模板方法模式,检查参数、同步互斥锁、doSetRate。doSetRate是RateLimiter的抽象方法,在子类SmoothRateLimiter的实现:

final void doSetRate(double permitsPerSecond, long nowMicros) {
  resync(nowMicros);
  double stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;
  this.stableIntervalMicros = stableIntervalMicros;
  doSetRate(permitsPerSecond, stableIntervalMicros);
}

又是一层模板方法。resync是SmoothRateLimiter的方法;doSetRate是SmoothRateLimiter的抽象方法,具体实现在子类中。stableIntervalMicros表示请求之间的间隔=1000μs/permitsPerSecond。

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

coolDownIntervalMicros是SmoothRateLimiter的抽象方法,在SmoothBursty中的实现是返回stableIntervalMicros。

resync注解中说到,如果nowMicros>nextFreeTicketMicros,也就是现在允许获取令牌,则计算newPermits=(当前时间-允许获取时间)/coolDownIntervalMicors,这里是向下取整的整除,计算结果是根据时间计算允许获取的令牌数。然后更新storedPermits=min(最大令牌数,storedPermits+newPermits),根据新允许的令牌数更新storedPermits,但不能超过桶容量maxPermits。最后将nextFreeTicketMicros重置为当前时间。

这里因为RateLimiter还处于创建状态,根据变量初始化值,会进到分支执行,maxPermits=0于是storedPermits=0,nextFreeTicketMicros=nowMicros,只是设置了下nextFreeTicketMicros为当前时间戳。

(注意:这里由于stableIntervalMicros还没有赋值,因此为除法且除数为0,但这里不会抛出异常因为是浮点数0.0,计算结果为Infinity)。

回到doSetRate方法,给stableIntervalMicros赋值之后调用doSetRate(permitsPerSecond, stableIntervalMicros),这是一个重载方法,它的参数列表是(double, double)。在子类SmoothBursty中的实现:

void doSetRate(double permitsPerSecond, double stableIntervalMicros) {
  double oldMaxPermits = this.maxPermits;
  maxPermits = maxBurstSeconds * permitsPerSecond;
  if (oldMaxPermits == Double.POSITIVE_INFINITY) {
    // if we don't special-case this, we would get storedPermits == NaN, below
    storedPermits = maxPermits;
  } else {
    storedPermits =
        (oldMaxPermits == 0.0)
            ? 0.0 // initial state
            : storedPermits * maxPermits / oldMaxPermits;
  }
}

先用oldMaxPermits保存老的maxPermits值,然后计算maxPermits=maxBurstSeconds*permitsPerSecond。前面已经赋值maxBurstSeconds=1,因此maxPermits就等于permitsPerSecond。接下来对oldMaxPermits上界的判断,如果是正常范围内,那么计算storedPermits,初始状态oldMaxPermits=0.0因此storedPermits为0。否则按照maxPermits/oldMaxPermits增长。如果只是在这个方法里看,其实maxPermits永远不会变的,storedPermits也就不会改变。所以初始状态这里做了storedPermits=0和maxPermits计算操作。

到这里,RateLimiter创建完成。而在使用时调用acquire又做了哪些事呢?

SmoothBursty获取令牌

public double acquire() {
  return acquire(1);
}

无参的acquire方法调用了有参的acquire(1):

public double acquire(int permits) {
  long microsToWait = reserve(permits);
  stopwatch.sleepMicrosUninterruptibly(microsToWait);
  return 1.0 * microsToWait / SECONDS.toMicros(1L);
}

可以看到,这个方法根据要求的令牌数计算需要等待的微秒数,然后使线程阻塞这些微秒即可放行。

stopwatch.sleepMicrosUninterruptibly(microsToWait); 使当前线程阻塞给定的微秒数。这方法用"Uninterrunptibly"表示不被中断的操作,即等待过程即使被中断了,也会优先进行等待操作直到过了给定的微秒数。

那么关键就在于如何根据令牌数计算需要等待的微秒数了,即reserve方法。

final long reserve(int permits) {
  checkPermits(permits);
  synchronized (mutex()) {
    return reserveAndGetWaitLength(permits, stopwatch.readMicros());
  }
}

首先检查下permits>0。acquire方法是加互斥锁的,内部又调用了reserveAndGetWaitLength方法。

final long reserveAndGetWaitLength(int permits, long nowMicros) {
  long momentAvailable = reserveEarliestAvailable(permits, nowMicros);
  return max(momentAvailable - nowMicros, 0);
}

reserveEarliestAvailable是RateLimiter的抽象方法,实现在SmoothRateLimiter中:

@Override
final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
  resync(nowMicros);
  long returnValue = nextFreeTicketMicros;
  double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
  double freshPermits = requiredPermits - storedPermitsToSpend;
  long waitMicros =
      storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
          + (long) (freshPermits * stableIntervalMicros);

  this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
  this.storedPermits -= storedPermitsToSpend;
  return returnValue;
}

reserveEarliestAvailable是SmoothRateLimiter的核心方法了,根据给定的请求数和当前时间(由于有互斥锁,nowMicros一定是拿到锁真正获取令牌时的时间),返回值是允许开始执行的时间。然后上层方法根据其返回值,计算需要等待的时间,使当前线程阻塞一段时间,即实现了限流操作。

这里涉及到的主要属性:

  • storedPermits: 桶中存储的令牌数,由resync方法根据时间动态增加桶中的令牌,其上限为maxPermits
  • nextFreeTicketMicros: 下一个令牌请求允许的时间戳
  • stableIntervalMicros: 根据每秒的限流数,计算得出的平均每一个令牌投放的时间

分析下这个方法的逻辑。首先,resync方法用于动态增加桶中的令牌。

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

当前时间大于下一个可允许的时间戳时,说明可能有新的令牌加入到桶中了,于是更新桶中的令牌数和下一次允许的时间戳。否则不更新。如果不更新的话,说明当前时间还不到允许获取令牌的时间,必定需要等待,此时storedPermits=0。

方法的返回值returnValue=nextFreeTicketMicros,而且中途没有修改,这说明当前线程等待的时间只和nextFreeTicketMicros有关。而nextFreeTicketMicros属性是在赋值returnValue之后有修改,因此SmoothBursty是允许瞬时突发流量的,比如我现在桶里一个都没有,每秒限流数是10,现在我要acquire(200)也是可以的,不需要等待,但是会影响下一个线程等待20s。

storedPermitsToSpend从桶中消耗的令牌数,=min(requiredPermits, this.storedPermits)

freshPermits 桶以外还需要消耗的令牌数,= requiredPermits - storedPermitsToSpend

this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros)将nextFreeTicketMicros追加waitMicros。LongMath.saturatedAdd是一个long型变量相加的方法,针对溢出情况做了特殊的处理(位运算。。。)。不得不说,谷歌的代码真是人如其名,见名知义,各种处理有一套,有水平


SmoothWarmingUp的创建

SmoothWarmingUp和SmoothBursty都是SmoothRateLimiter的子类。这种没有实际用过,结合注释来看似乎是这样的,SmoothBursty允许瞬时的突发的流量,令牌分发的速率是恒定的,即每1/permitsPerSecond时间分发一个令牌。当一个巨大的流量到来时,允许瞬时的请求量到达permitsPerSecond,然后按照每秒permitsPerSecond控制请求率。而SmoothWarmingUp会控制令牌分发的速率,在这个速率到达恒定分发速率前有一段"warm up period"。

还是来看代码,对应的入口函数为:

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

根据permitsPerSecond,warmup时间创建一个SmoothWarmingUp的实例。内部调用了子方法create:

static RateLimiter create(
    SleepingStopwatch stopwatch,
    double permitsPerSecond,
    long warmupPeriod,
    TimeUnit unit,
    double coldFactor) {
  RateLimiter rateLimiter = new SmoothWarmingUp(stopwatch, warmupPeriod, unit, coldFactor);
  rateLimiter.setRate(permitsPerSecond);
  return rateLimiter;
}

调用子方法,秒表和SmoothBursty的一样,根据系统时间来计算的秒表。一个coldFactor参数固定为3.0,可以注意一下。

子方法第一行是创建SmoothWarmingUp实例,内部也是赋值操作。warmupPeriodMicros:warmup阶段微秒数,coldFactor=3.0.

第二行是一个熟悉的setRate方法,入参只有permitsSecond,其内部是一个同步方法:

public final void setRate(double permitsPerSecond) {
  checkArgument(
      permitsPerSecond > 0.0 && !Double.isNaN(permitsPerSecond), "rate must be positive");
  synchronized (mutex()) {
    doSetRate(permitsPerSecond, stopwatch.readMicros());
  }
}

进入SmoothRateLimiter的doSetRate模板方法。

final void doSetRate(double permitsPerSecond, long nowMicros) {
  resync(nowMicros);
  double stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;
  this.stableIntervalMicros = stableIntervalMicros;
  doSetRate(permitsPerSecond, stableIntervalMicros);
}

又是resync方法。。

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

和SmoothBursty差不多,这里也会进到分支里。

coolDownIntervalMicros方法也就是令牌投放速率,返回值是warmupPeriodMicros / maxPermits,和入参挂钩了。但在此处,maxPermits=0,因此返回值是一个无穷大的数。下面storedPermits=0,nextFreeTicketMicros=nowMicros。因此看来初始化的resync和SmoothBursty没啥区别,只是设置了nextFreeTicketMicros为创建时间。

回到doSetRate方法,第二、三行计算stableIntervalMicros,注意这是SmoothRateLimiter的属性。

第四行调用doSetRate的重载方法,因此要进入SmoothWarmingUp的实现来看。

void doSetRate(double permitsPerSecond, double stableIntervalMicros) {
  double oldMaxPermits = maxPermits;
  double coldIntervalMicros = stableIntervalMicros * coldFactor;
  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 {
    storedPermits =
        (oldMaxPermits == 0.0)
            ? maxPermits // initial state is cold
            : storedPermits * maxPermits / oldMaxPermits;
  }
}

一系列属性的赋值操作。。

这段代码涉及到几个SmoothWarmingUp的特有变量,thresholdPermits, maxPermits, slope。
c o l d I n t e r v a l M i c r o s = s t a b l e I n t e r v a l M i c r o s ∗ c o l d F a c t o r coldIntervalMicros = stableIntervalMicros*coldFactor coldIntervalMicros=stableIntervalMicroscoldFactor

t h r e s h o l d P e r m i t s = 0.5 ∗ w a r m u p P e r i o d M i c r o s s t a b l e I n t e r v a l M i c r o s thresholdPermits = 0.5*\frac{warmupPeriodMicros}{stableIntervalMicros} thresholdPermits=0.5stableIntervalMicroswarmupPeriodMicros

thresholdPermits=0.5*warmupPeriodMicros/stableIntervalMicros,看变量名像是一个介于0和最大值的阈值,这里的warmupPeriodMicros和最初创建时传入的参数有关。
m a x P e r m i t s = t h r e s h o l d P e r m i t s + 2.0 ∗ w a r m u p P e r i o d M i c r o s s t a b l e I n t e r v a l M i c r o s + c o l d I n t e r v a l M i c r o s = 0.5 ∗ w a r m u p P e r i o d M i c r o s s t a b l e I n t e r v a l M i c r o s + 2.0 ∗ w a r m u p P e r i o d M i c r o s ( 1 + c o l d F a c t o r ) ∗ s t a b l e I n t e r v a l M i c r o s = ( 0.5 + 2.0 1 + c o l d F a c t o r ) w a r m u p P e r i o d M i c r o s s t a b l e I n t e r v a l M i c r o s maxPermits=thresholdPermits+\frac{2.0*warmupPeriodMicros}{stableIntervalMicros+coldIntervalMicros}\\ = 0.5*\frac{warmupPeriodMicros}{stableIntervalMicros} + 2.0*\frac{warmupPeriodMicros}{(1+coldFactor)*stableIntervalMicros}\\ = (0.5+\frac{2.0}{1+coldFactor})\frac{warmupPeriodMicros}{stableIntervalMicros} maxPermits=thresholdPermits+stableIntervalMicros+coldIntervalMicros2.0warmupPeriodMicros=0.5stableIntervalMicroswarmupPeriodMicros+2.0(1+coldFactor)stableIntervalMicroswarmupPeriodMicros=(0.5+1+coldFactor2.0)stableIntervalMicroswarmupPeriodMicros
可见,当coldFactor=3.0时,maxPermits刚好可以化简为a=2*thresholdPermits=warmupPeriodMicros/stableIntervals ,分子和分母都和输入参数有关。而SmoothBursty的maxPermits=permitsPerSecond。

因此在SmoothWarmingUp中,如果warmupPeriod传入1s,则maxPermits也为permitsPerSecond。
s l o p e = c o l d I n t e r v a l M i c r o s − s t a b l e I n t e r v a l M i c r o s m a x P e r m i t s − t h r e s h o l d P e r m i t s = ( c o l d F a c t o r − 1 ) ∗ s t a b l e I n t e r v a l M i c r o s ∗ ( 1 + c o l d F a c t o r ) ∗ s t a b l e I n t e r v a l M i c r o s 2.0 ∗ w a r m u p P e r i o d M i c r o s = c o l d F a c t o r 2 − 1 2.0 ∗ s t a b l e I n t e r v a l M i c r o s 2 w a r m u p P e r i o d M i c r o s slope = \frac{coldIntervalMicros-stableIntervalMicros}{maxPermits-thresholdPermits}\\ =(coldFactor-1)*stableIntervalMicros *\frac{(1+coldFactor)*stableIntervalMicros}{2.0*warmupPeriodMicros}\\ =\frac{coldFactor^2-1}{2.0}*\frac{stableIntervalMicros^2}{warmupPeriodMicros} slope=maxPermitsthresholdPermitscoldIntervalMicrosstableIntervalMicros=(coldFactor1)stableIntervalMicros2.0warmupPeriodMicros(1+coldFactor)stableIntervalMicros=2.0coldFactor21warmupPeriodMicrosstableIntervalMicros2

slope似乎是介于阈值和最大值的部分投放令牌的速率有关。

storedPermits的初值是maxPermits,和SmoothBursty略有不同(初值为0)。

再来看获取令牌的方法,acquire一步步进到最里面SmoothRateLimiter.reserveEarliestAvailable方法

SmoothWarmingUp获取令牌

@Override
final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
  resync(nowMicros);
  long returnValue = nextFreeTicketMicros;
  double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
  double freshPermits = requiredPermits - storedPermitsToSpend;
  long waitMicros =
      storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
          + (long) (freshPermits * stableIntervalMicros);

  this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
  this.storedPermits -= storedPermitsToSpend;
  return returnValue;
}

还是一个父类的模板方法,首先调用了resync。

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

和前面分析的一样,resync方法的作用是当前可以获取新令牌时,根据当前时间和允许时间的差值计算更新桶中的令牌数,并将允许的时间重置为当前时间。这里resync的方法是一个方法,有所不同的是coolDownIntervalMicros()方法在SmoothWarmingUp中的实现是:

double coolDownIntervalMicros() {
  return warmupPeriodMicros / maxPermits;
}

即桶中增加令牌的速率,和输入参数warmupPeriodMicros挂钩了。不过根据上面化简的结果,coldFactor=3时,其返回值也等于stableIntervalMicros。所以默认行为仍然是按照固定速率向桶中投放令牌,和SmoothBursty没啥区别。

再回到reserveEarliestAvailable方法里面。由于同样是父类SmoothRateLimiter的模板方法,整体上逻辑还是一致的。当前线程获取令牌直接放行,影响的是下一次请求进来等待的时间。计算下一次请求等待时间,仍然是对桶中和桶以外的令牌分别计算;桶以外的令牌按照平均速率stableIntervalMicros计算。桶以内的令牌就有一些区别了。storedPermitsToWaitTime方法的入参有两个,桶中的令牌数和要消耗桶中令牌的个数。还记得SmoothBursty的实现直接返回0,即桶中的令牌允许一次性全部消耗,且不耗费时间。因此SmoothBursty和SmoothWarmingup的根本区别应该是在这里,即桶中的令牌消耗是否是瞬时的?还是需要视情况耗费一些时间?来看SmoothWarmingup的实现吧。

@Override
long storedPermitsToWaitTime(double storedPermits, double permitsToTake) {
  double availablePermitsAboveThreshold = storedPermits - thresholdPermits;
  long micros = 0;
  // measuring the integral on the right part of the function (the climbing line)
  if (availablePermitsAboveThreshold > 0.0) {
    double permitsAboveThresholdToTake = min(availablePermitsAboveThreshold, permitsToTake);
    // TODO(cpovirk): Figure out a good name for this variable.
    double length = permitsToTime(availablePermitsAboveThreshold)
        + permitsToTime(availablePermitsAboveThreshold - permitsAboveThresholdToTake);
    micros = (long) (permitsAboveThresholdToTake * length / 2.0);
    permitsToTake -= permitsAboveThresholdToTake;
  }
  // measuring the integral on the left part of the function (the horizontal line)
  micros += (stableIntervalMicros * permitsToTake);
  return micros;
}

private double permitsToTime(double permits) {
  return stableIntervalMicros + permits * slope;
}

方法也有个几行,好在这些方法拆分的不错,只有一个子方法,再给谷歌大佬点个烟。。

首先计算了下availablePermitsAboveThreshold,是桶中存储的在阈值以上部分的令牌数。如果大于0,那么进入到分支里;如果小于0,那么会按照平均速率消耗令牌(注意这里是桶中的消耗,有耗时的)。

如果桶中的令牌数超过了阈值,那么首先从阈值之上消耗令牌,计算permitsAboveThresholdToTake为将要在阈值之上消耗的令牌数,然后计算消耗时间为permitsAboveThresholdToTake*length/2。
l e n g t h = 2 ∗ s t a b l e I n t e r v a l M i c r o s + s l o p e ∗ ( 2 ∗ a v a i l a b l e P e r m i t s A b o v e T h r e s h o l d − p e r m i t s A b o v e T h r e s h o l d T o T a k e ) = 2 ∗ s t a b l e I n t e r v a l M i c r o s + c o l d F a c t o r 2 − 1 2.0 ∗ s t a b l e I n t e r v a l M i c r o s 2 w a r m u p P e r i o d M i c r o s ∗ ( 2 ∗ a v a i l a b l e P e r m i t s A b o v e T h r e s h o l d − p e r m i t s A b o v e T h r e s h o l d T o T a k e ) length = 2*stableIntervalMicros + slope*(2*availablePermitsAboveThreshold-permitsAboveThresholdToTake)\\ = 2*stableIntervalMicros+\frac{coldFactor^2-1}{2.0}*\frac{stableIntervalMicros^2}{warmupPeriodMicros}*(2*availablePermitsAboveThreshold-permitsAboveThresholdToTake)\\ length=2stableIntervalMicros+slope(2availablePermitsAboveThresholdpermitsAboveThresholdToTake)=2stableIntervalMicros+2.0coldFactor21warmupPeriodMicrosstableIntervalMicros2(2availablePermitsAboveThresholdpermitsAboveThresholdToTake)
length其实是一个速率,即消耗单位令牌所需的时间,但是这个计算方法比较复杂。。整体看一下,其实速率是length/2,比stableIntervalMicros还多了后面这一大串。

这一段还有待理解。。不过大概可以看出来,计算桶中令牌消耗时间的方法是分两步算的,首先算阈值以上消耗的时间,阈值以上的令牌平均消耗时间是大于stableIntervalMicros的;然后计算阈值一下消耗的时间,按照平均stableIntervalMicros的速度消耗。而桶以外也是按照stableIntervalMicros速度消耗的。

SmoothBursty和SmoothWarmingUp的联系和区别。通过计算等待时间并阻塞实现限流,当前线程获取的请求数直接影响下一次请求需要等待的时间。不同的是SmoothBursty允许桶中的令牌立刻消耗尽,SmoothWarmingUp有一个"warmup"时间,而且桶中的令牌同样需要消耗时间。因此,假设是一个一个的请求,SmoothBursty可以在第一秒就达到预期的速率值,并且由于瞬时消耗,第一秒的请求可以在很短时间内连续发生;而SmoothWarmingUp第一秒达不到预期的速率值,需要经过一个warmup预热期,在达到桶容量阈值以前消耗速率逐渐加快,到达阈值以后按照匀速消耗,即按照预期的速率消耗。消耗桶以外的令牌二者都是按照匀速来执行的。因此区别就在于对于桶中的令牌消耗是否瞬时。SmoothBursty适合允许某一时间突发流量的情况,而SmoothWarmingUp适合要求请求速率缓慢提升的情况。SmoothWarmingUp内部coldFactor默认为3.0,输入参数受warmupPeriods影响,因此具体性能需要结合实际情况调整尝试。


附一个本地测试的代码:

    public static void main(String[] args) throws InterruptedException {
        RateLimiter rateLimiter = RateLimiter.create(10);
        long l1 = System.currentTimeMillis();
        rateLimiter.acquire(10);
        long l2 = System.currentTimeMillis();
        rateLimiter.acquire(10);
        long l3 = System.currentTimeMillis();
        System.out.println((l2-l1) + "," + (l3-l2));
        Thread.sleep(1000);
        long l4 = System.currentTimeMillis();
        rateLimiter.acquire(200);
        System.out.println(l4-l3);
    }

输出:

1,1003
1010
;