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();@Overrideprotected long readMicros() {return stopwatch.elapsed(MICROSECONDS);}@Overrideprotected 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 nowif (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, belowstoredPermits = 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 nowif (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 nowif (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, belowstoredPermits = 0.0;} else {storedPermits =(oldMaxPermits == 0.0)? maxPermits // initial state is cold: storedPermits * maxPermits / oldMaxPermits;}
}

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

这段代码涉及到几个SmoothWarmingUp的特有变量,thresholdPermits, maxPermits, slope。
coldIntervalMicros=stableIntervalMicros∗coldFactorcoldIntervalMicros = stableIntervalMicros*coldFactor coldIntervalMicros=stableIntervalMicros∗coldFactor

thresholdPermits=0.5∗warmupPeriodMicrosstableIntervalMicrosthresholdPermits = 0.5*\frac{warmupPeriodMicros}{stableIntervalMicros} thresholdPermits=0.5∗stableIntervalMicroswarmupPeriodMicros​

thresholdPermits=0.5*warmupPeriodMicros/stableIntervalMicros,看变量名像是一个介于0和最大值的阈值,这里的warmupPeriodMicros和最初创建时传入的参数有关。
maxPermits=thresholdPermits+2.0∗warmupPeriodMicrosstableIntervalMicros+coldIntervalMicros=0.5∗warmupPeriodMicrosstableIntervalMicros+2.0∗warmupPeriodMicros(1+coldFactor)∗stableIntervalMicros=(0.5+2.01+coldFactor)warmupPeriodMicrosstableIntervalMicrosmaxPermits=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。
slope=coldIntervalMicros−stableIntervalMicrosmaxPermits−thresholdPermits=(coldFactor−1)∗stableIntervalMicros∗(1+coldFactor)∗stableIntervalMicros2.0∗warmupPeriodMicros=coldFactor2−12.0∗stableIntervalMicros2warmupPeriodMicrosslope = \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 nowif (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。
length=2∗stableIntervalMicros+slope∗(2∗availablePermitsAboveThreshold−permitsAboveThresholdToTake)=2∗stableIntervalMicros+coldFactor2−12.0∗stableIntervalMicros2warmupPeriodMicros∗(2∗availablePermitsAboveThreshold−permitsAboveThresholdToTake)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

RateLimiter google限流组件试析(SmoothBursty/SmoothWarmingUp)相关推荐

  1. 【Guava】使用Guava的RateLimiter做限流

    2019独角兽企业重金招聘Python工程师标准>>> 一.常见的限流算法 目前常用的限流算法有两个:漏桶算法和令牌桶算法. 1.漏桶算法 漏桶算法的原理比较简单,请求进入到漏桶中, ...

  2. 使用Guava的RateLimiter做限流

    一.问题描述   某天A君突然发现自己的接口请求量突然涨到之前的10倍,没多久该接口几乎不可使用,并引发连锁反应导致整个系统崩溃.如何应对这种情况呢?生活给了我们答案:比如老式电闸都安装了保险丝,一旦 ...

  3. RateLimiter实现限流

    简介 令牌桶算法则是一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌.桶中存放的令牌数有最大上限,超出之后就被丢弃或者拒绝.当流量或者网络请求到达时,每个请求都要获取一个令牌,如果能够获取到,则直 ...

  4. alibaba sentinel限流组件 源码分析

    如何使用? maven引入: <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>s ...

  5. 基于.net的分布式系统限流组件

    在互联网应用中,流量洪峰是常有的事情.在应对流量洪峰时,通用的处理模式一般有排队.限流,这样可以非常直接有效的保护系统,防止系统被打爆.另外,通过限流技术手段,可以让整个系统的运行更加平稳.今天要与大 ...

  6. Alibaba限流组件——Sentinel核心概念与流量控制

    目录 1 Sentinel介绍 1.1 Sentinel是什么 1.2 组成 1.3 关键概念 2 Sentinel流量控制案例 2.1 引入依赖 2.2 使用Sentinel提供的API实现流量控制 ...

  7. 10万+QPS秒杀限流组件设计与实现

    我们在简化版1万+QPS(https://blog.csdn.net/luozhonghua2014/article/details/80384061)设计架构上增强限流设计来应对100万+QPS峰值 ...

  8. 超详细的Guava RateLimiter限流原理解析

    点击上方"方志朋",选择"置顶或者星标" 你的关注意义重大! 限流是保护高并发系统的三把利器之一,另外两个是缓存和降级.限流在很多场景中用来限制并发和请求量,比 ...

  9. Guava RateLimiter限流原理解析

    来源:https://zhuanlan.zhihu.com/p/60979444 限流是保护高并发系统的三把利器之一,另外两个是缓存和降级.限流在很多场景中用来限制并发和请求量,比如说秒杀抢购,保护自 ...

最新文章

  1. Task04:青少年软件编程(Scratch)等级考试模拟卷(一级)
  2. lwip路由实现_基于LWIP协议栈对路由缓存数据结构实现改进设计
  3. 当会打王者荣耀的AI学会踢足球,一不小心拿下世界冠军!
  4. Python文件处理
  5. etymon word write alb pain high alt increase large agency ag lose weight fat assist out~3
  6. python+opencv获取最小外接矩形
  7. 一些常用的gcc指令(持续更新)
  8. ICCV 2019 | SPM:单阶段人体姿态估计解决方案
  9. java 三位数的水仙花数
  10. 蓝奏云批量下载v0.3修复版
  11. php生成随机密码的几种方法
  12. JAVA学习之类与对象例题分享(两点确定直线并进行相关操作)
  13. (计算机显示器主屏幕区域)桌面造句,部编版《语文园地四》教学反思模板(11页)-原创力文档...
  14. GIT回滚master分支到指定tag版本 并提交远程仓库
  15. 【SpringBoot整合NoSql】-----ElasticSearch的安装与操作篇
  16. alios thing - rhino内核 - 内存管理
  17. 高等数学:第六章 定积分的应用(4)平面曲线的弧长
  18. Java多线程篇--concurrentHashMap
  19. 记录第一次使用nvidia tao训练模型的过程
  20. 汽车行业部件IPX9K高温高压喷水试验测试

热门文章

  1. STM32F103三路ADC同步转换带有DMA功能
  2. [881]内存不足RuntimeError: CUDA out of memory. Tried to allocate 16.00 MiB (GPU 0; 2.00 GiB total cap...
  3. 黑客大曝光:恶意软件和Rootkit安全
  4. 《Linux与Python 编程 R》--实验指导书(2020)
  5. 深度图像+rgb转化点云数据、点云数据打开、显示以及保存
  6. PTA-离散数学图论自测(有答案)
  7. lr增强细节_Adobe Lightroom CC二月更新:基于AI的增强细节功能
  8. (实用详细)快速入门北斗短报文RDSS协议/北斗协议
  9. 【原创】JS+COOKIES实现健壮的购物车!
  10. Flask 结合 Highcharts 实现动态渲染图表