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=stableIntervalMicros∗coldFactor
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.5∗stableIntervalMicroswarmupPeriodMicros
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.0∗warmupPeriodMicros=0.5∗stableIntervalMicroswarmupPeriodMicros+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=maxPermits−thresholdPermitscoldIntervalMicros−stableIntervalMicros=(coldFactor−1)∗stableIntervalMicros∗2.0∗warmupPeriodMicros(1+coldFactor)∗stableIntervalMicros=2.0coldFactor2−1∗warmupPeriodMicrosstableIntervalMicros2
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=2∗stableIntervalMicros+slope∗(2∗availablePermitsAboveThreshold−permitsAboveThresholdToTake)=2∗stableIntervalMicros+2.0coldFactor2−1∗warmupPeriodMicrosstableIntervalMicros2∗(2∗availablePermitsAboveThreshold−permitsAboveThresholdToTake)
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