限流是保护高并发系统的三把利器之一,另外两个是缓存和降级。
常用的限流算法有滑动窗口、令牌桶和和漏桶等。Guava中的RateLimiter使用的是令牌桶,对比其他,该算法可容忍一定突发流量的速率的限流,通过控制桶的容量、发放令牌的速率,来达到对请求的限制。
如上图:令牌桶算法会维护⼀个令牌( token )桶,以⼀个恒定的速度往桶⾥放⼊令牌( token ):当令牌桶满了的时候,再向其中放令牌,那么多余的令牌会被丢弃;当想要处理一个请求的时候,需要从令牌桶中取出一个令牌,如果此时令牌桶中没有令牌,那么则拒绝该请求。
1、guava RateLimiter使用
RateLimiter只能通过设置“令牌生成速率”(permits per second)来控制流量,无法显式设置桶的容量(burst size)。桶的容量是隐式管理的,支持以下两种模式:
- SmoothBursty(平滑突发):只要桶内有令牌就直接放行,也就是说系统会直接面对这突发的大量请求;这种模式下,桶的最大令牌数=permitsPerSecond (见下文解析)
- SmoothWarmingUp(平滑预热):认为系统在闲置一段时间后可能会存在缓存大量失效的情况,把这种情况下的系统的状态称为cool冷却状态,这种情况下系统不能达到最大的性能,如果直接面对大量的请求可能存在系统崩溃的状态,所以应该存在一个预热状态,也就是逐渐增多放行的请求数量,给系统一个缓冲时间。这种模式下,桶的最大令牌数= thresholdPermits + 2.0 * warmupPeriodMicros / (stableIntervalMicros + coldIntervalMicros)。(见下文解析)
1.1)示例:
import com.google.common.util.concurrent.RateLimiter;
import java.util.concurrent.TimeUnit;
public class RateLimiterExample {
public static void main(String[] args) {
// SmoothBursty:允许突发流量
RateLimiter burstyLimiter = RateLimiter.create(1.0);
//burstyLimiter.setRate(10.0); 重新调整速率
for (int i = 0; i < 10; i++) {
final int finalI = i;
new Thread(() -> { // 使用burstyLimiter
burstyLimiter.acquire();
System.out.println("请求 " + finalI + ",时间:" + System.currentTimeMillis());
}).start();
}
// SmoothWarmingUp:预热10秒,令牌生成速率逐渐升高到5 QPS
RateLimiter warmingUpLimiter = RateLimiter.create(5.0, 10, TimeUnit.SECONDS);
// 使用warmingUpLimiter
System.out.println("SmoothWarmingUp模式:");
for (int i = 0; i < 10; i++) {
final int finalI = i;
new Thread(() -> { // 使用burstyLimiter
warmingUpLimiter.acquire();
System.out.println("请求 " + i + ",时间:" + System.currentTimeMillis());
}).start();
}
}
}
输出:
SmoothBursty模式:
请求 1,时间:1751290470946
请求 9,时间:1751290471951
请求 7,时间:1751290472950
请求 6,时间:1751290473948
请求 8,时间:1751290474950
请求 5,时间:1751290475948
请求 3,时间:1751290476950
请求 4,时间:1751290477947
请求 2,时间:1751290478947
请求 0,时间:1751290479950
可以看到:guava RateLimiter不保证线程的公平,由于设置的qps=1,所以每个线程打印的时间基本是相差1000.
1.2)api说明:
- create(double permitsPerSecond):创建一个限流器,参数是每秒允许的请求数(QPS),默认是平滑突发模式。对于平滑突发模式,桶的最大容量=permitsPerSecond。
- double acquire()/acquire(int permits):请求一个/n个令牌,如果当前没有令牌,则会阻塞直到令牌可用。返回值是获取令牌等待的时间(单位秒)
- boolean tryAcquire() /tryAcquire(int permits):尝试获取1个/n个令牌,获取不到时立即返回 false,适合非阻塞限流。
- boolean tryAcquire(int permits, long timeout, TimeUnit unit):获取permits个令牌,最多阻塞等待timeout时间
- setRate(double permitsPerSecond):设置令牌发放速率。RateLimiter允许在运行中调整速率。
注:RateLimiter 是线程安全的,它会限制所有线程的调用总速率,但它并不保证公平性。
1.3)其他单机限流组件:
- Resilience4j:功能丰富的限流、熔断、重试库,支持多种限流策略。
- Bucket4j:基于令牌桶算法的限流库,支持分布式限流。
- Sentinel(阿里开源):功能强大的流量控制组件,支持多种限流规则。
2、RateLimiter实现原理
传统的令牌桶算法中,有一个固定容量的桶,并以稳定的速率生成令牌放在桶中。当有请求时,需要获取令牌才能通过。因为桶的容量固定,令牌满了就不生产了。桶中有充足的令牌时,突发的流量可以直接获取令牌通过。当令牌取光后,后面的请求就得等生成令牌后才能通过。
RateLimiter的令牌桶算法并不是真的有个令牌桶对象,以每秒定时任务式地往桶中生产指定个数的令牌对象。后面介绍RateLimiter到底是如何以非定时任务的方式实现令牌桶算法的?
2.1)RateLimiter如何生成令牌放到桶里?——延迟计算令牌
1)思路:
根据令牌桶算法,桶中的令牌是持续生成存放的,有请求时需要先从桶中拿到令牌才能开始执行。根据这个特点,很容易想到:
开启一个定时任务,由定时任务持续生成令牌。这样的问题在于会极大的消耗系统资源,如,某接口需要分别对每个用户做访问频率限制,假设系统中存在6W用户,则至多需要开启6W个定时任务来维持每个桶中的令牌数,这样的开销是巨大的。
RateLimiter使用延迟计算令牌的方式:通过维护一个“下一次可用许可(令牌)的时间戳”(nextFreeTicketMicros)来控制许可发放节奏,同时允许令牌积累,但积累的最大量是有限制的。主要原理是:允许第一个请求预消费桶中的令牌,下一个请求则需要等待生产“上次预消费令牌数量”的时间。
- 在每次acquire请求获取特定数量的令牌是,先按照【当前时间和上一次计算令牌的时间的差值/产生令牌的时间间隔】计算出当前时刻桶内应该有多少个令牌(是一个浮点数),将生成的令牌加入令牌桶中并更新令牌数。这样一来,只需要在获取令牌时计算一次令牌数,而不是定时生产令牌。
- 计算得到桶内令牌数后,根据请求的令牌数和当前现有的令牌数,通过一定的规则计算出本次请求需要等待的时间是多少(可以是0),然后让请求线程进行阻塞等待这么多时间。(第一个请求可以预消费——不用等待,但后续请求需要先把上次欠的令牌还完后再消费)
- 请求线程从acquire请求处苏醒,继续执行后续代码。这样,RateLimiter对这个请求做限流的工作就完成了。
注意:请求的许可数量不会影响请求本身的限流(调用 acquire(1) 和调用 acquire(1000) 会导致完全相同的节流,即使两者有差异),但会影响下一个请求的节流。也就是说,如果一个开销较大的任务到达空闲的 RateLimiter,它会立即被授予许可,但下一个请求将受到额外的节流,从而弥补这个开销较大的任务的开销。
2)说明:
- RateLimiter实际上有两种限流器实现类:SmoothBursty限流器和SmoothWarmingUp限流器。它们的核心区别其实仅在于第2步中,去计算本次请求令牌需要等待的时间的方式是不同的。具体体现为reserveEarliestAvailable()内调用的计算等待时间的方法有不同的重写。
- 在第2步中,计算出来的等待时间也可能是0,即不需要等待(请求线程不需要被阻塞),请求线程直接能马上获取到令牌。
- RateLimiter一定保证请求线程能拿到它想要的全部令牌,尽管拿到这些令牌可能需要等一段时间。这个也是比较创新的一点
3)源码结构:
RateLimiter是一个抽象类,SmoothRateLimiter(本身也是一个抽象类)实现了该抽象类;在SmoothRateLimiter中定义了两个static的子实现类,分别是:SmoothWarmingUp和SmoothBursty。查看SmoothRateLimiter,定义了以下属性:
abstract class SmoothRateLimiter extends RateLimiter {
static final class SmoothWarmingUp extends SmoothRateLimiter {
}
static final class SmoothBursty extends SmoothRateLimiter {
}
/**当前存储令牌数 The currently stored permits. */
double storedPermits;
/**最大存储令牌数 The maximum number of stored permits. */
double maxPermits;
/**
* 添加(生成)令牌时间间隔
* 无论哪个子类,计算方式都是:stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;
*/
double stableIntervalMicros;
/**
* 下一次请求可以获取令牌的起始时间,由于RateLimiter允许一次预消费,上次请求预消费令牌后,下次请求需要等待相应的时间到nextFreeTicketMicros时刻才可以获取令牌
*/
private long nextFreeTicketMicros = 0L; // could be either in the past or future
}
2.2)请求获取令牌过程
RateLimiter本身是线程安全的,在实际业务中
- 如果你的限流逻辑是全局的,且需要在整个应用程序中共享同一个RateLimiter实例,可以使用单例模式(例如在spring中设置成一个bean)。
- 如果每个需要限流的业务逻辑都有自己独立的RateLimiter实例,那么需要多个RateLimiter实例。
1)整体流程:
对于某个RateLimiter实例,多个线程并发调用acquire()方式进行流量控制,内部调用链如下:
- reserve :每个线程同步阻塞的方式更新桶令牌、nextFreeTicketMicros,以及计算需要等待的时间
- sleepMicrosUninterruptibly:每个线程进行等待阻塞(内部通过sleep实现)
所以,从这里可以看到,令牌的发放通过惰性的方式放在了每次请求中,而不是通过定时器来完成。
public abstract class RateLimiter {
public double acquire(int permits) {
long microsToWait = reserve(permits); //更新桶令牌、nextFreeTicketMicros,以及计算需要等待的时间
stopwatch.sleepMicrosUninterruptibly(microsToWait); //执行等待
return 1.0 * microsToWait / SECONDS.toMicros(1L); //返回等待时间(转成秒)
}
final long reserve(int permits) {
checkPermits(permits);
synchronized (mutex()) {
return reserveAndGetWaitLength(permits, stopwatch.readMicros());
}
}
final long reserveAndGetWaitLength(int permits, long nowMicros) {
long momentAvailable = reserveEarliestAvailable(permits, nowMicros);
return max(momentAvailable - nowMicros, 0);
}
abstract long reserveEarliestAvailable(int permits, long nowMicros);
}
说明1:在执行等待的时候,是通过Uninterruptibles.sleepUninterruptibly实现的,它本质上是包装了Thread.sleep(),但是会:
- 忽略中断(不抛出InterruptedException)
- 确保睡眠完整的时间
- 最后恢复中断状态
说明2:当并发场景中,多个线程调用acquire方法时,阻塞会发生在两个阶段:
- 同步阶段:在synchronized内计算等待时间、更新桶内令牌数和nextFreeTicketMicros(非常短暂)
- 等待阶段:通过sleep让请求的线程等待
这样设计好处是:实际等待在同步块外进行,避免持有锁时睡眠。同步块只用于计算等待时间,不用于实际等待,同步块内确保了令牌计算和状态更新的原子性,防止并发问题。
2)reserve方法拆解和同步阻塞中使用的锁:
reserve是RateLimiter最核心的方法,主要功能:生成桶内令牌、根据请求的令牌数计算nextFreeTicketMicros、计算本次请求的等待时间。内部通过synchronized确保多线程的互斥(同步阻塞)。调用链:reserveAndGetWaitLength -> reserveEarliestAvailable:
public abstract class RateLimiter {
// Can't be initialized in the constructor because mocks don't call the constructor.
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;
}
//更新桶内令牌、nextFreeTicketMicros和计算本次请求等待时间
final long reserve(int permits) {
checkPermits(permits);
synchronized (mutex()) {
return reserveAndGetWaitLength(permits, stopwatch.readMicros()); //stopwatch.readMicros()获取的当前时间
}
}
// 需要等待的时间(单位micros)
final long reserveAndGetWaitLength(int permits, long nowMicros) {
long momentAvailable = reserveEarliestAvailable(permits, nowMicros); // 返回当前请求可以获取到令牌的最早可用时间点(单位为微秒)
return max(momentAvailable - nowMicros, 0); //计算出等待时间,0表示不用等待
}
abstract long reserveEarliestAvailable(int permits, long nowMicros);
}
RateLimiter使用了双重检验锁的方式创建了一个Object对象,所有线程都利用这个对象来进行资源的互斥,reserve、setRate、getRate、tryAcquire关键操作都通过synchronized同步控制。reserve方法内部方法调用:
- 调用rsync方法,根据nowNicros>nextFreeTicketMicros判断是否处于空闲期,从而生成新的令牌;
- 将当前nextFreeTicketMicros的值作为reserveEarliestAvailable的返回值(long returnValue = nextFreeTicketMicros;)作为本次获取令牌的最早可用时间;
- 根据本次请求的令牌数和桶内已有令牌数,计算出一个生成这些令牌所需的等待时间,并更新nextFreeTicketMicros(作为下次获取令牌的最早可用时间)和桶内令牌数storedPermits
- 根据第二部的返回值,通过max(momentAvailable - nowMicros, 0);计算出来本次请求需要等待时间。
注意:两个时间都和nextFreeTicketMicros变量有关,
- 本次请求等待时间:根据当前nextFreeTicketMicros值计算;
- 下次获取可用令牌的时间:会根据本次请求的令牌数和桶内令牌数进行计算,然后更新nextFreeTicketMicros值,也就是下次请求需要等待时间。
3)reserveEarliestAvailable方法拆解:
该方法每次会调用rsync做一次令牌生成的逻辑(只有“空闲期”才会生成新令牌)。然后,会根据本次请求的令牌数、桶内已有令牌数(初始值=0)更新nextFreeTicketMicros和桶内令牌数。
abstract class SmoothRateLimiter extends RateLimiter {
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;
}
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()和storedPermitsToWaitTime()这两个方法,前者影响生成新令牌的数量;后者影响下次请求获取可用令牌的时间(也就是下次请求等待时间)。在不同子类中实现方式不同,对于SmoothBursty子类实现的比较简单:
- coolDownIntervalMicros方法:返回stableIntervalMicros,返回指定令牌速度的时间间隔,stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;(即初始化限流器出入参数permitsPerSecond的倒数)
- storedPermitsToWaitTime方法:直接返回0
对于SmoothWarmingUp子类实现,【见下面2.3】
A、resync方法:
当nowMicros <= nextFreeTicketMicros:表示有“欠账”不需要发放令牌;当nowMicros > nextFreeTicketMicros,表示没有"欠账",此时可以计算闲置期间应积累的令牌,更新令牌数、nextFreeTicketMicros:
- 新进的令牌数newPermits = 闲置时间 / 冷却间隔时间,对于SmoothBursty,冷却间隔时间=stableIntervalMicros,即令牌发放速率间隔时间,也就是空闲时间段该生成多少个令牌
- 将新令牌加入存储,不超过最大限制maxPermits,对于SmoothBursty,maxPermits = 1秒的令牌数(也就是创建时指定的参数permitsPerSecond)【见下面2.3】
- 将下次令牌可用时间更新为当前时间,由于这个条件是闲置期间,所以可以肯定本次请求不需要等待,从计算等待时间也可以得出来max(momentAvailable - nowMicros, 0);等待时间一定是0
B、reserveEarliestAvailable剩下的方法:
- 该方法首先会讲当前的nextFreeTicketMicros赋值给returnValue作为返回值(代码第四行long returnValue = nextFreeTicketMicros; ),从而用来计算本次请求的等待时间。
- 根据本次请求的令牌数、桶内已有令牌数(初始值=0),计算出消耗这些令牌需要的时间(waitMicros),然后更新nextFreeTicketMicros(作为计算下次请求等待时间)和桶内令牌数。
对于SmoothBursty子类,waitMicros = freshPermits * stableIntervalMicros,freshPermits是本次请求的令牌数和桶内令牌数差的绝对值,stableIntervalMicros表示令牌发放速率间隔时间(初始化限流器出入参数的倒数)。
示例:5qps场景下的行为:
当设置为5qps时,理论上是每200毫秒(1000/5)允许一个请求,大量请求时:
a. 第一个线程获取令牌并立即通过
b. 后续线程会依次计算需要等待的时间(如200ms, 400ms, 600ms...)
c. 每个线程会在自己的等待时间上睡眠
2.3)SmoothBursty创建过程分析:
SmoothBursty是一种稳定的限流器。
1)源码
public abstract class RateLimiter {
public static RateLimiter create(double permitsPerSecond) {
return create(permitsPerSecond, SleepingStopwatch.createFromSystemTimer());
}
static RateLimiter create(double permitsPerSecond, SleepingStopwatch stopwatch) {
RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */);
rateLimiter.setRate(permitsPerSecond);
return rateLimiter;
}
public final void setRate(double permitsPerSecond) {
synchronized (mutex()) {
doSetRate(permitsPerSecond, stopwatch.readMicros());
}
}
abstract void doSetRate(double permitsPerSecond, long nowMicros);
}
abstract class SmoothRateLimiter extends RateLimiter {
@Override
final void doSetRate(double permitsPerSecond, long nowMicros) {
resync(nowMicros);
double stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;
this.stableIntervalMicros = stableIntervalMicros;
doSetRate(permitsPerSecond, stableIntervalMicros);
}
static final class SmoothBursty extends SmoothRateLimiter {
final double maxBurstSeconds;
SmoothBursty(SleepingStopwatch stopwatch, double maxBurstSeconds) {
super(stopwatch);
this.maxBurstSeconds = maxBurstSeconds;
}
@Override
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;
}
}
@Override
long storedPermitsToWaitTime(double storedPermits, double permitsToTake) {
return 0L;
}
@Override
double coolDownIntervalMicros() {
return stableIntervalMicros;
}
}
}
A、创建流程:
在创建SmoothBursty时会调用setRate方法,该方法内部会同步调用doRate方法,首先调用rsync,然后计算出stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;
B、在doSetRate方法中,为什么要保存oldMaxPermits?
先保存oldMaxPermits是为了正确处理storedPermits的重新计算。这里有几个关键点:
- 当速率改变时,maxPermits会被重新计算(maxPermits = maxBurstSeconds * permitsPerSecond)
-
- 但storedPermits需要根据新旧maxPermits的比例进行调整,以保持存储许可的相对比例不变
- 如果不保存旧值,就无法正确计算新的storedPermits值
- 避免NaN出现的场景:
- 当oldMaxPermits是Double.POSITIVE_INFINITY时,如果不特殊处理,在计算storedPermits * maxPermits / oldMaxPermits时会出现Infinity/Infinity的情况,这会得到NaN。这种情况可能发生在:初始创建RateLimiter时当RateLimiter被配置为无限速率时(比如通过RateLimiter.create(Double.POSITIVE_INFINITY))
2)SmoothBrusty桶大小
令牌桶算法的优势之一就是允许保留一定的令牌数在桶中(最多积累到桶的容量大小),这样就可以应对突增的流量。Google Guava 的 RateLimiter 是基于令牌桶算法实现的,对于SmoothBrusty,允许积累最多1秒的令牌数(也就是创建时指定的参数permitsPerSecond),避免了“因为调用时间稍有延迟导致后续请求必须等待”的问题。即: maxPermits = maxBurstSeconds * permitsPerSecond;其中maxBurstSeconds = 1.
2.4)SmoothWarmingUp创建过程解读:
SmoothWarmingUp 是 Guava RateLimiter 中的一种预热限流算法,它的工作原理可以总结如下:
- 通过预热机制平滑地从冷启动状态过渡到稳定速率
- 避免突发流量对系统造成冲击,特别适用于需要"热身"的系统(如缓存预热、JVM JIT编译等场景)
这种算法通过数学建模实现了从冷启动到稳定状态的平滑过渡,既能保护系统不被突发流量冲垮,又能充分利用系统资源。
注:SmoothWarmingUp会根据系统的使用情况动态在预热区和稳定区之间切换
- 当 RateLimiter 刚创建时,storedPermits 会被初始化为 maxPermits(冷启动状态),第一次请求会消耗大量存储许可,直接进入预热区计算
- 从冷启动恢复:当系统长期闲置后,storedPermits 会逐渐累积到 maxPermits,此时新的请求会再次进入预热区
- 突发流量处理:当短时间内消耗大量许可导致 storedPermits < thresholdPermits 时,会自动切换到稳定区
- 持续稳定状态:当系统保持稳定速率使用时,storedPermits 会维持在 thresholdPermits 以下,持续使用稳定区计算
1)源码:
public abstract class RateLimiter {
public static RateLimiter create(double permitsPerSecond, Duration warmupPeriod) {
return create(permitsPerSecond, toNanosSaturated(warmupPeriod), TimeUnit.NANOSECONDS);
}
public static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit) {
return create(
permitsPerSecond, warmupPeriod, unit, 3.0, SleepingStopwatch.createFromSystemTimer());
}
static RateLimiter create(
double permitsPerSecond,
long warmupPeriod,
TimeUnit unit,
double coldFactor,
SleepingStopwatch stopwatch) {
RateLimiter rateLimiter = new SmoothWarmingUp(stopwatch, warmupPeriod, unit, coldFactor);
rateLimiter.setRate(permitsPerSecond);
return rateLimiter;
}
public final void setRate(double permitsPerSecond) {
synchronized (mutex()) {
doSetRate(permitsPerSecond, stopwatch.readMicros());
}
}
abstract void doSetRate(double permitsPerSecond, long nowMicros);
}
abstract class SmoothRateLimiter extends RateLimiter {
@Override
final void doSetRate(double permitsPerSecond, long nowMicros) {
resync(nowMicros);
double stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;
this.stableIntervalMicros = stableIntervalMicros;
doSetRate(permitsPerSecond, stableIntervalMicros);
}
static final class SmoothWarmingUp extends SmoothRateLimiter {
private final long warmupPeriodMicros;
private double slope;
private double thresholdPermits;
private double coldFactor;
SmoothWarmingUp(
SleepingStopwatch stopwatch, long warmupPeriod, TimeUnit timeUnit, double coldFactor) {
super(stopwatch);
this.warmupPeriodMicros = timeUnit.toMicros(warmupPeriod);
this.coldFactor = coldFactor;
}
@Override
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;
}
}
@Override
long storedPermitsToWaitTime(double storedPermits, double permitsToTake) {
//见下面
}
private double permitsToTime(double permits) {
return stableIntervalMicros + permits * slope;
}
@Override
double coolDownIntervalMicros() {
return warmupPeriodMicros / maxPermits;
}
}
}
参数:warmupPeriod,预热时间,表示RateLimiter 在达到稳定(最大)速率之前提高速率的持续时间
- warmupPeriodMicros:预热时间(微秒)
- coldFactor:冷启动因子(默认3),表示冷启动时的速率是稳定速率的 1/3
- thresholdPermits:阈值许可数,区分稳定区和预热区,计算方法:0.5 * warmupPeriodMicros / stableIntervalMicros,也可以理解成:0.5 * permitsPerSecond * warmupPeriod(秒)
- maxPermits:最大许可数,计算方法:thresholdPermits + 2.0 * warmupPeriodMicros / (stableIntervalMicros + coldIntervalMicros)。
2)工作流程:
- 令牌存储:
- 当系统空闲时,会逐渐累积令牌到 maxPermits
- 许可消耗:
- 当消耗的令牌数在 thresholdPermits 以下时,使用稳定速率
- 当消耗的令牌数超过 thresholdPermits 时,进入预热区,速率会逐渐变慢
3)使用梯形面积模型计算等待时间:
SmoothWarmingUp使用了梯型面积模型计算等待时间
- 稳定区(0 - thresholdPermits):矩形面积,等待时间 = 许可数 × 稳定间隔
- 预热区(thresholdPermits - maxPermits):梯形面积,等待时间 = (上底+下底)×高/2
如图:
代码:
@Override
long storedPermitsToWaitTime(double storedPermits, double permitsToTake) {
double availablePermitsAboveThreshold = storedPermits - thresholdPermits;
long micros = 0;
// 预热区计算(梯形面积)
if (availablePermitsAboveThreshold > 0.0) {
double permitsAboveThresholdToTake = min(availablePermitsAboveThreshold, permitsToTake);
double length =
permitsToTime(availablePermitsAboveThreshold) + // 上底
permitsToTime(availablePermitsAboveThreshold - permitsAboveThresholdToTake); // 下底
micros = (long) (permitsAboveThresholdToTake * length / 2.0); // 梯形面积公式
permitsToTake -= permitsAboveThresholdToTake;
}
// 稳定区计算(矩形面积)
micros += (long) (stableIntervalMicros * permitsToTake);
return micros;
}
private double permitsToTime(double permits) {
return stableIntervalMicros + permits * slope; // 线性函数计算间隔时间
}