Sentinel源码—5.FlowSlot借鉴Guava的限流算法二
大纲
1.Guava提供的RateLimiter限流使用示例
2.Guava提供的RateLimiter简介与设计
3.继承RateLimiter的SmoothBursty源码
4.继承RateLimiter的SmoothWarmingUp源码
3.继承RateLimiter的SmoothBursty源码
(1)SmoothBursty的初始化流程
(2)SmoothBursty的初始化完成后的变量值
(3)SmoothBursty的acquire()和tryAcquire()
(4)SmoothBursty的令牌生成规则分析
(5)SmoothRateLimiter对预支令牌的处理分析
(6)SmoothBursty的案例场景分析
(1)SmoothBursty的初始化流程
令牌桶算法是可以应对突发流量的,Bursty则有突发的含义。SmoothBursty应对突发流量是有前提条件的,只有在令牌桶内有存储的令牌情况下,才会放行相应的突发流量,而令牌桶内的已存储令牌是低流量时省下来的。如果系统一直处于高流量,导致令牌桶内没有存储的令牌,那么当突发流量过来时,也只能按照固定速率放行。
所以在SmoothBursty类中,获取令牌桶中的存储令牌是无需额外代价的。当令牌桶能满足请求线程所需的令牌数量时,就不会阻塞线程,从而达到应对突发流量的能力。当然,令牌桶中的存储令牌是有上限的,该上限会通过构造方法进行设置。
首先,new SmoothBursty(stopwatch, 1.0)构造方法表示的是:通过硬编码指定了令牌桶中最多存储1秒的令牌数。如果传入的permitsPerSecond = 10,表示的是每秒生成10个令牌,那么意味着令牌桶中最多存储10个令牌。
然后,初始化SmoothBursty的重点是RateLimiter的setRate()方法。该方法会调用SmoothRateLimiter的doSetRate()方法,然后调用SmoothRateLimiter的resync()方法,最后调用SmoothBursty的doSetRate()设定maxPermits和storedPermits。
@Beta
@GwtIncompatible
@SuppressWarnings("GoodTime")
public abstract class RateLimiter {...//Creates a RateLimiter with the specified stable throughput, //given as "permits per second" (commonly referred to as QPS, queries per second).//The returned RateLimiter ensures that on average no more than permitsPerSecond are issued during any given second, //with sustained requests being smoothly spread over each second.//When the incoming request rate exceeds permitsPerSecond the rate limiter will release one permit every (1.0 / permitsPerSecond) seconds. //When the rate limiter is unused, bursts of up to permitsPerSecond permits will be allowed, //with subsequent requests being smoothly limited at the stable rate of permitsPerSecond.//创建一个具有指定稳定吞吐量的RateLimiter,传入的"permits per second"通常称为QPS、每秒查询量;//返回的RateLimiter确保在任何给定的秒期间平均不超过permitsPerSecond的令牌被发出,持续的请求将在每一秒内被平稳地通过;//当传入请求的速率超过permitsPerSecond时,速率限制器将每隔(1.0/permitsPerSecond)秒释放一个令牌;//当速率限制器未被使用时,将允许突发式的高达permitsPerSecond的令牌,而随后的请求将以permitsPerSecond的稳定速率被平滑地限制;//对外暴露的创建方法//@param permitsPerSecond the rate of the returned RateLimiter, measured in how many permits become available per second.public static RateLimiter create(double permitsPerSecond) {//The default RateLimiter configuration can save the unused permits of up to one second. //This is to avoid unnecessary stalls in situations like this: //A RateLimiter of 1qps, and 4 threads, all calling acquire() at these moments://T0 at 0 seconds、T1 at 1.05 seconds、T2 at 2 seconds、T3 at 3 seconds//Due to the slight delay of T1, T2 would have to sleep till 2.05 seconds, and T3 would also have to sleep till 3.05 seconds.//默认的RateLimiter配置可以保存长达一秒钟的未被使用的令牌;//这是为了避免在这种情况下出现不必要的停顿://一个由1QPS和4个线程组成的RateLimiter,所有线程都在如下这些时刻调用acquired()://Thread0在0秒、Thread1在1.05秒、Thread2在2秒、Thread3在3秒//由于Thread1的轻微延迟,Thread2必须睡眠到2.05秒,Thread3也必须睡眠到3.05秒//内部调用一个QPS设定 + 起始时间StopWatch的构建函数.//这里传入的SleepingStopwatch是一个以系统启动时间的一个相对时间的计量.//后面的读时间偏移是以这个开始的时间偏移为起始的.return create(permitsPerSecond, SleepingStopwatch.createFromSystemTimer());}@VisibleForTestingstatic RateLimiter create(double permitsPerSecond, SleepingStopwatch stopwatch) {//指定了令牌桶中最多存储1秒的令牌数RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */);//调用RateLimiter的setRate()方法rateLimiter.setRate(permitsPerSecond);return rateLimiter;}//Updates the stable rate of this RateLimiter, //that is, the permitsPerSecond argument provided in the factory method that constructed the RateLimiter. //Currently throttled threads will not be awakened as a result of this invocation, //thus they do not observe the new rate; only subsequent requests will.//Note though that, since each request repays (by waiting, if necessary) the cost of the previous request, //this means that the very next request after an invocation to setRate() will not be affected by the new rate; //it will pay the cost of the previous request, which is in terms of the previous rate.//The behavior of the RateLimiter is not modified in any other way, //e.g. if the RateLimiter was configured with a warmup period of 20 seconds, //it still has a warmup period of 20 seconds after this method invocation.//更新该RateLimiter的稳定速率,即在构造RateLimiter的工厂方法中提供permitsPerSecond参数;//当前被限流的线程将不会由于这个调用而被唤醒,因此它们没有观察到新的速率;只有随后的请求才会;//但是要注意的是,由于每个请求(如果需要,通过等待)会偿还先前请求的成本,//这意味着调用setRate()方法后的下一个请求将不会受到新速率的影响,//它将按照先前的速率处理先前请求的成本;//RateLimiter的行为不会以任何其他方式修改,//例如:如果RateLimiter被配置为具有20秒的预热周期,在该方法调用之后,它仍然有20秒的预热期;//@param permitsPerSecond the new stable rate of this {@code RateLimiter}public final void setRate(double permitsPerSecond) {checkArgument(permitsPerSecond > 0.0 && !Double.isNaN(permitsPerSecond), "rate must be positive");//在同步代码块中设定速率synchronized (mutex()) {//调用SmoothRateLimiter.doSetRate()方法doSetRate(permitsPerSecond, stopwatch.readMicros());}}...
}@GwtIncompatible
abstract class SmoothRateLimiter extends RateLimiter {//The currently stored permits.//令牌桶中当前缓存的未消耗的令牌数double storedPermits;//The maximum number of stored permits.//令牌桶中允许存放的最大令牌数double maxPermits;//The interval between two unit requests, at our stable rate.//E.g., a stable rate of 5 permits per second has a stable interval of 200ms.//按照我们稳定的速率,两个单位请求之间的时间间隔;例如,每秒5个令牌的稳定速率具有200ms的稳定间隔double stableIntervalMicros;//The time when the next request (no matter its size) will be granted. //After granting a request, this is pushed further in the future. Large requests push this further than small requests.//下一个请求(无论大小)将被批准的时间.//在批准请求后,这将在未来进一步推进,大请求比小请求更能推动这一进程。private long nextFreeTicketMicros = 0L;//could be either in the past or future...//这是一个可以重复调用的函数.//第一次调用和非第一次调用的过程有些不一样,目的是设定设定最大令牌数maxPermits和已存储的令牌数storedPermits@Overridefinal void doSetRate(double permitsPerSecond, long nowMicros) {//调用SmoothRateLimiter.resync()方法,重试计算和同步存储的预分配的令牌.resync(nowMicros);//计算稳定的发放令牌的时间间隔. 单位us, 比如QPS为5, 则为200ms的间隔进行令牌发放. double stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;this.stableIntervalMicros = stableIntervalMicros;//调用SmoothBursty.doSetRate()设定最大令牌数maxPermits和已存储的令牌数storedPermitsdoSetRate(permitsPerSecond, stableIntervalMicros);}//Updates storedPermits and nextFreeTicketMicros based on the current time.//根据当前时间,更新storedPermits和nextFreeTicketMicros变量//注意: 在初始化SmoothBursty时会第一次调用resync()方法,此时各值的情况如下://coolDownIntervalMicros = 0、nextFreeTicketMicros = 0、newPermits = 无穷大.//maxPermits = 0(初始值,还没有重新计算)、最后得到的: storedPermits = 0;//同时,nextFreeTicketMicros = "起始时间"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;}}abstract void doSetRate(double permitsPerSecond, double stableIntervalMicros);...//This implements a "bursty" RateLimiter, where storedPermits are translated to zero throttling.//The maximum number of permits that can be saved (when the RateLimiter is unused) is defined in terms of time, //in this sense: if a RateLimiter is 2qps, and this time is specified as 10 seconds, we can save up to 2 * 10 = 20 permits.//SmoothBursty实现了一个"突发式"的速率限制器RateLimiter,其中的storedPermits会被转换为0;//它可以保存的最大令牌数量(当RateLimiter未使用时)是根据时间定义的,//从这个意义上说:如果RateLimiter是2QPS,并且这个时间被指定为10秒,那么最多可以保存2 * 10 = 20个令牌;static final class SmoothBursty extends SmoothRateLimiter {//The work (permits) of how many seconds can be saved up if this RateLimiter is unused?//如果这个速率限制器RateLimiter没有被使用,那么可以节省多少秒的工作(令牌)?final double maxBurstSeconds;SmoothBursty(SleepingStopwatch stopwatch, double maxBurstSeconds) {super(stopwatch);this.maxBurstSeconds = maxBurstSeconds;}@Overridevoid doSetRate(double permitsPerSecond, double stableIntervalMicros) {//初次设定的时候,oldMaxPermits = 0.0double oldMaxPermits = this.maxPermits;//新的(当前的)maxPermits为burst的时间周期(1秒) * 每周期的令牌数.maxPermits = maxBurstSeconds * permitsPerSecond;if (oldMaxPermits == Double.POSITIVE_INFINITY) {//if we don't special-case this, we would get storedPermits == NaN, belowstoredPermits = maxPermits;} else {//初始化SmoothBursty,执行到此处时,storedPermits为0storedPermits = (oldMaxPermits == 0.0) ? 0.0 : storedPermits * maxPermits / oldMaxPermits;}}@Overridelong storedPermitsToWaitTime(double storedPermits, double permitsToTake) {return 0L;}@Overridedouble coolDownIntervalMicros() {return stableIntervalMicros;}}...
}
(2)SmoothBursty的初始化完成后的变量值
在构建完SmoothBursty这个RateLimiter后,其初始状态说明如下:
说明一:maxBurstSeconds为1秒。默认情况下,传入的突发周期参数为1秒。
说明二:storedPermits为0。没有预分配的令牌,因为此时还处于初始的状态。
说明三:stableIntervalMicros表示的是每个令牌发放时的时间间隔,会根据给定的QPS换算出来。
说明四:maxPermits表示的是最大允许存储的令牌个数(= 突发周期 * 每周期允许数),这里突发周期限定为1秒,也就是可以预存储一个周期的令牌。
说明五:nextFreeTicketMicros表示的是下一次可以发放令牌的起始时间,会被初始化为"开始时间"。
(3)SmoothBursty的acquire()和tryAcquire()
一.RateLimiter实现限流的过程
二.SmoothBursty的acquire()方法分析
三.SmoothBursty的tryAcquire()方法分析
一.RateLimiter的限流过程
RateLimiter的限流过程可以分为如下四个步骤:
步骤一:生产令牌
步骤二:获取令牌
步骤三:计算阻塞时间
步骤四:阻塞线程
既然RateLimiter做了抽象,那么说明它提取了限流过程中的共性,而RateLimiter里的共性就是阻塞线程的逻辑。即RateLimiter的acquire()方法将阻塞线程这个共性提取了出来,将生产令牌、获取令牌、计算阻塞时间的具体细节由子类去实现。RateLimiter的子类SmoothRateLimiter的几个重要属性如下:
属性一:nextFreeTicketMicros
表示的是下一次请求被允许的时间。当令牌数不足时,会由处理当前请求的线程延迟计算令牌生成数及耗时。即使需要等待,当前线程也不会去阻塞等待,而是提前预支令牌。而这个预支的代价会转嫁给下一个请求,这样做的目的是为了减少线程阻塞。
属性二:stableIntervalMicros
表示的是每产生一个令牌需要消耗的微秒,这个值是根据构造器传入的permitsPerSecond换算成微秒数得来的。
属性三:maxPermits
表示的是令牌桶中允许存放的最大令牌数。
属性四:storedPermits
表示的是令牌桶中当前缓存的未消耗的令牌数。当令牌消耗速度小于令牌产生速度时,令牌桶内就会开始堆积令牌,但是storedPermits不会大于maxPermits。
二.SmoothBursty的acquire()方法分析
执行SmoothBursty的acquire()方法时,会对令牌对象加synchronized锁。通过加synchronized锁让并发的请求进行互斥,才能实现限流效果。其中SmoothRateLimiter的reserveEarliestAvailable()方法的细节说明如下:
说明一:该方法主要用来实现生产令牌、获取令牌、计算阻塞时间
计算阻塞时间时,会将总的阻塞时间拆分成两部分。第一部分是从桶中获取storedPermitsToSpend个现有令牌的代价,第二部分是等待生成freshPermits个新鲜令牌的代价。
对于子类,生成新鲜令牌的代价是相同的,只有获取现有令牌代价才会不同。所以从桶中获取令牌需要等待的时间的抽象方法storedPermitsToWaitTime()会由SmoothRateLimiter子类实现。其中的一个子类SmoothBursty的storedPermitsToWaitTime()方法返回0,表示不需要等待。
说明二:获取令牌的阻塞代价会转移给下一个请求
如果处理当前请求时发现需要阻塞等待,那么等待时间由下个请求承受。这样做的目的是为了减少线程的阻塞。因为下一个请求的请求时间是不确定的,可能很久后才到来下一个请求。而这段时间内生成的新鲜令牌已经可以满足下一个请求了,从而不用阻塞。
@Beta
@GwtIncompatible
@SuppressWarnings("GoodTime")
public abstract class RateLimiter {...//无限等待的获取//Acquires the given number of permits from this RateLimiter, //blocking until the request can be granted. //Tells the amount of time slept, if any.//@param permits the number of permits to acquire,获取的令牌数量//@return time spent sleeping to enforce rate, in seconds; 0.0 if not rate-limited@CanIgnoreReturnValuepublic double acquire(int permits) {//调用RateLimiter.reserve()方法//预支令牌并获取需要阻塞的时间:即预定数量为permits的令牌数,并返回需要等待的时间long microsToWait = reserve(permits);//将需要等待的时间补齐, 从而满足限流的需求,即根据microsToWait来让线程sleep(共性)stopwatch.sleepMicrosUninterruptibly(microsToWait);//返回这次调用使用了多少时间给调用者return 1.0 * microsToWait / SECONDS.toMicros(1L);}//Reserves the given number of permits from this RateLimiter for future use, //returning the number of microseconds until the reservation can be consumed.//从这个RateLimiter限速器中保留给定数量的令牌,以备将来使用,返回可以使用保留前的微秒数//@return time in microseconds to wait until the resource can be acquired, never negativefinal long reserve(int permits) {checkPermits(permits);//由于涉及并发操作,所以必须使用synchronized进行互斥处理synchronized (mutex()) {//调用RateLimiter.reserveAndGetWaitLength()方法return reserveAndGetWaitLength(permits, stopwatch.readMicros());}}//Reserves next ticket and returns the wait time that the caller must wait for.//预定下一个ticket,并且返回需要等待的时间final long reserveAndGetWaitLength(int permits, long nowMicros) {//调用SmoothRateLimiter.reserveEarliestAvailable()方法long momentAvailable = reserveEarliestAvailable(permits, nowMicros);return max(momentAvailable - nowMicros, 0);}//Reserves the requested number of permits and returns the time that those permits can be used (with one caveat).//保留请求数量的令牌,并返回可以使用这些令牌的时间(有一个警告)//生产令牌、获取令牌、计算阻塞时间的具体细节由子类来实现//@return the time that the permits may be used, or, if the permits may be used immediately, an arbitrary past or present timeabstract long reserveEarliestAvailable(int permits, long nowMicros);...
}@GwtIncompatible
abstract class SmoothRateLimiter extends RateLimiter {//The currently stored permits. //令牌桶中当前缓存的未消耗的令牌数double storedPermits;//The maximum number of stored permits.//令牌桶中允许存放的最大令牌数double maxPermits;//The interval between two unit requests, at our stable rate.//E.g., a stable rate of 5 permits per second has a stable interval of 200ms.//按照我们稳定的速率,两个单位请求之间的时间间隔;例如,每秒5个令牌的稳定速率具有200ms的稳定间隔double stableIntervalMicros;//The time when the next request (no matter its size) will be granted. //After granting a request, this is pushed further in the future. Large requests push this further than small requests.//下一个请求(无论大小)将被批准的时间. 在批准请求后,这将在未来进一步推进,大请求比小请求更能推动这一进程.private long nextFreeTicketMicros = 0L;//could be either in the past or future...@Overridefinal long reserveEarliestAvailable(int requiredPermits, long nowMicros) {//1.根据nextFreeTicketMicros计算新产生的令牌数,更新当前未使用的令牌数storedPermits//获取令牌时调用SmoothRateLimiter.resync()方法与初始化时的调用不一样.//此时会把"还没有使用"的令牌存储起来.//但是如果计数时间nextFreeTicketMicros是在未来. 那就不做任何处理.resync(nowMicros);//下一个请求(无论大小)将被批准的时间,这个值将被作为方法结果返回long returnValue = nextFreeTicketMicros;//2.计算需要阻塞等待的时间//2.1.先从桶中取未消耗的令牌,如果桶中令牌数不足,看最多能取多少个//存储的令牌可供消费的数量double storedPermitsToSpend = min(requiredPermits, this.storedPermits);//2.2.计算是否需要等待新鲜的令牌(当桶中现有的令牌数不足时就需要等待新鲜的令牌),如果需要,则计算需要等待的令牌数//需要等待的令牌:新鲜的令牌double freshPermits = requiredPermits - storedPermitsToSpend;//计算需要等待的时间//分两部分计算:waitMicros = 从桶中获取storedPermitsToSpend个现有令牌的代价 + 等待生成freshPermits个新鲜令牌的代价//从桶中取storedPermitsToSpend个现有令牌也是有代价的,storedPermitsToWaitTime()方法是个抽象方法,会由SmoothBursty和SmoothWarmingUp实现//对于SmoothBursty来说,storedPermitsToWaitTime()会返回0,表示已经存储的令牌不需要等待.//而生成新鲜令牌需要等待的代价是:新鲜令牌的个数freshPermits * 每个令牌的耗时stableIntervalMicroslong waitMicros = storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend) + (long) (freshPermits * stableIntervalMicros);//3.更新nextFreeTicketMicros//由于新鲜的令牌可能已被预消费,所以nextFreeTicketMicros就得往后移,以表示这段时间被预消费了this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);//4.扣减令牌数,更新桶内剩余令牌//最后把上面计算的可扣减的令牌数量从存储的令牌里减掉this.storedPermits -= storedPermitsToSpend;//返回请求需要等待的时间//需要注意returnValue被赋值的是上次的nextFreeTicketMicros,说明当前这次请求获取令牌的代价由下一个请求去支付return returnValue;}//Updates storedPermits and nextFreeTicketMicros based on the current time.//根据当前时间,更新storedPermits和nextFreeTicketMicros变量//计算nextFreeTicketMicros到当前时间内新产生的令牌数,这个就是延迟计算void resync(long nowMicros) {//if nextFreeTicket is in the past, resync to now//一般当前的时间是大于下个请求被批准的时间//此时:会把过去的时间换成令牌数存储起来,注意存储的令牌数不能大于最大的令牌数//当RateLimiter初始化好后,可能刚开始没有流量,或者是一段时间没有流量后突然来了流量//此时可以往"后"预存储一秒时间的令牌数. 也就是这里所说的burst能力//如果nextFreeTicketMicros在未来的一个时间点,那这个if判断便不满足//此时,不需要进行更新storedPermits和nextFreeTicketMicros变量//此种情况发生在:"预借"了令牌的时候if (nowMicros > nextFreeTicketMicros) {//时间差除以生成一个新鲜令牌的耗时,coolDownIntervalMicros()是抽象方法,由子类实现double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();//更新令牌桶内已存储的令牌个数,注意不超过最大限制storedPermits = min(maxPermits, storedPermits + newPermits);//更新nextFreeTicketMicros为当前时间nextFreeTicketMicros = nowMicros;}}//Translates a specified portion of our currently stored permits which we want to spend/acquire, into a throttling time.//Conceptually, this evaluates the integral of the underlying function we use, for the range of [(storedPermits - permitsToTake), storedPermits].//This always holds: 0 <= permitsToTake <= storedPermits//从桶中取出已存储的令牌的代价,由子类实现//这是一个抽象函数,SmoothBursty中的实现会直接返回0,可以认为已经预分配的令牌,在获取时不需要待待时间abstract long storedPermitsToWaitTime(double storedPermits, double permitsToTake);//Returns the number of microseconds during cool down that we have to wait to get a new permit.//每生成一个新鲜令牌的耗时,由子类实现abstract double coolDownIntervalMicros();...static final class SmoothBursty extends SmoothRateLimiter {...@Overridelong storedPermitsToWaitTime(double storedPermits, double permitsToTake) {return 0L;}@Overridedouble coolDownIntervalMicros() {return stableIntervalMicros;}}...
}
三.SmoothBursty的tryAcquire()方法分析
其实就是在acquire()方法的基础上,增加了如下判断:如果当前时间 + 超时时间 > nextFreeTicketMicros,那么就可以继续尝试。
@Beta
@GwtIncompatible
@SuppressWarnings("GoodTime")
public abstract class RateLimiter {... //有超时时间的获取//@param permits the number of permits to acquire,获取的令牌数量//@param timeout the maximum time to wait for the permits. Negative values are treated as zero.//@param unit the time unit of the timeout argument//@return true if the permits were acquired, false otherwisepublic boolean tryAcquire(int permits, long timeout, TimeUnit unit) {long timeoutMicros = max(unit.toMicros(timeout), 0);checkPermits(permits);long microsToWait;synchronized (mutex()) {long nowMicros = stopwatch.readMicros();//调用RateLimiter.canAcquire()方法看是否超时if (!canAcquire(nowMicros, timeoutMicros)) {return false;} else {microsToWait = reserveAndGetWaitLength(permits, nowMicros);}}stopwatch.sleepMicrosUninterruptibly(microsToWait);return true;}private boolean canAcquire(long nowMicros, long timeoutMicros) {//SmoothRateLimiter.queryEarliestAvailable()方法会返回nextFreeTicketMicros//如果当前时间nowMicros + 超时时间timeoutMicros > nextFreeTicketMicros,那么就可以继续等待尝试获取return queryEarliestAvailable(nowMicros) - timeoutMicros <= nowMicros;}//Returns the earliest time that permits are available (with one caveat).//@return the time that permits are available, or, if permits are available immediately, an arbitrary past or present timeabstract long queryEarliestAvailable(long nowMicros);...
}@GwtIncompatible
abstract class SmoothRateLimiter extends RateLimiter {//The time when the next request (no matter its size) will be granted. //After granting a request, this is pushed further in the future. Large requests push this further than small requests.//下一个请求(无论大小)将被批准的时间. 在批准请求后,这将在未来进一步推进,大请求比小请求更能推动这一进程.private long nextFreeTicketMicros = 0L;//could be either in the past or future...@Overridefinal long queryEarliestAvailable(long nowMicros) {return nextFreeTicketMicros;}...
}
(4)SmoothBursty的令牌生成规则分析
SmoothBursty的消费原理示意图如下:

青色代表已经存储的令牌(permits),紫色代表新同步时间产生的令牌(permits)。
一.分析时间t1
在时刻t1请求的令牌数量是permits_01,此时青色中已经存储了部分令牌,但是不够。同步时间轴后,产生了部分紫色的新令牌。重新计算可用令牌,青色与紫色加一起。如果两者之和超过了一个Burst周期,则取周期的最大值maxPermits。此时消费后,还剩余部分的令牌,在图中的t2时刻表示为青色部分,而且在t2时刻时nextFreeTicketMicros已经被标记为t1。
二.分析时间t2
在时刻t2请求的令牌数量是pertmits_02,此时青色部分代表的已存储令牌数不够。同步时间轴后,会产生紫色部分的令牌。但是此时,已经产生的令牌数量还是不够消费。因此需要睡眠一个由freshPermits转换的时间间隔,然后nextFreeTicketMicros被更新到t2时刻的未来时间,即产生了预支。如果在nextFreeTicketMicros这个时间点到来之前,直接调用tryAcquire(),并且给定的超时时间太短,那么tryAcquire()就会返回失败。
(5)SmoothRateLimiter对预支令牌的处理分析
Smooth的含义是平稳的、平滑的,Bursty的含义是突发式的、突发性的。
SmoothRateLimiter类型的RateLimiter,在没有被消费的前提下,可以最多预存1秒的令牌来应对突发式流量。
关于nextFreeTicketMicros所表示的"下一个请求被批准的时间"补充说明:在一般场景下,它是一个过去的值,此时可以预存令牌。在初始化场景下,它会初始化为nowMicros,此时无法应对突发式流量。
对于任意时刻,SmoothRateLimiter都可以向以后的时间预支令牌来消费。但所预支令牌的时间成本,当前消费者不承担,由下一个消费者承担。这可参考SmoothRateLimiter.reserveEarliestAvailable()方法中的处理,也就是当前请求不进行等待,而是由下一次请求进行等待。
每次执行SmoothRateLimiter的reserveEarliestAvailable()方法时,都会先调用SmoothRateLimiter的resync()方法更新nextFreeTicketMicros。但如果存在预支令牌的情况,该方法是不会更新nextFreeTicketMicros的。因为预支令牌时,nextFreeTicketMicros是未来的时间,大于nowMicros。然后SmoothRateLimiter的reserveEarliestAvailable()方法最后会返回执行SmoothRateLimiter的resync()方法后的nextFreeTicketMicros,让处理当前请求的线程睡眠阻塞到nextFreeTicketMicros这个时间点。
在SmoothRateLimiter.reserveEarliestAvailable()方法中,由于先执行SmoothRateLimiter的resync()方法更新nextFreeTicketMicros。所以只要处理当前请求时,上一个请求没有出现预支令牌的情况,也就是nextFreeTicketMicros比nowMicros小的时候。那么即使当前请求需要申请的令牌数不够,当前请求也不需要进行等待,只需要向往后的时间去借足够的令牌即可立刻返回。
(6)SmoothBursty的案例场景分析
当程序已经完成了初始化,但是没有任何流量,持续了很长的时间。此时来了一个acquire(200)的请求,不管已经存储的令牌有多少,处理该请求时都可消费这些已存储的令牌,并且在不够时可以借后面时间产生的令牌 + 不需要等待。
但下一个请求acquire(250)到来时可能就没有这么幸运了,处理这个请求可能需要等待上一个请求所预支令牌的生成时间。比如上一个请求是借了后面时间产生的50个令牌,那么处理当前请求时就需要先等待生成这50个令牌的时间。如果等待完成之后,当前请求还需要额外的100个令牌,那么当前请求还需借后面时间产生的100个令牌。再下一个请求的处理,以此类推。
4.继承RateLimiter的SmoothWarmingUp源码
(1)SmoothWarmingUp的介绍
(2)SmoothWarmingUp的初始化
(3)SmoothWarmingUp中maxPermits的计算
(4)SmoothWarmingUp获取令牌
(5)SmoothBursty和SmoothWarmingUp的对比
(1)SmoothWarmingUp的介绍
SmoothWarmingUp支持预热功能,预热是对于冷系统来说的。当系统流量低时,系统就会冷下来,具体表现在:线程池会释放多余线程、连接池会释放多余连接、缓存会过期失效等。这时如果还放行满负荷流量甚至突发流量进入冷系统,则系统压力会暴增。
系统压力暴增很容易会导致系统出现问题,这也是SmoothBursty的不足。因为在SmoothBursty的实现逻辑里,流量低时桶内存储的令牌会增多。此时如果有满负荷流量甚至突发流量进入系统,SmoothBursty会放行,从而对系统产生比较大的压力。所以不能简单根据桶内是否有存储的令牌来放行流量,要判断系统冷热程度。
简单来说就是:流量越低时,桶内堆积的令牌数就会越高(因为生成速度大于消耗速度),而系统就会越冷,这时令牌生成速率就应该要越低,从而达到预热的目的。

上图中的变量含义如下:
变量一:coldIntervalMicros
表示的是系统最冷时的令牌生成速率,这时单位令牌的耗时最大。
变量二:stableIntervalMicros
表示的是稳定阶段每生成一个令牌需要消耗的微秒数,这个值是根据构造方法传入的permitsPerSecond换算成微秒数得来的。
变量三:maxPermits
表示的是令牌桶中允许存放的最大令牌数。
变量四:storedPermits
表示的是令牌桶中当前缓存的未消耗的令牌数。当令牌消耗速度小于令牌产生速度时,桶内就会开始堆积令牌。但是堆积的令牌数不会大于maxPermits,这个值越大,说明系统越冷。
变量五:thresholdPermits
表示的是进入预热阶段的临界令牌数。当桶内存储的令牌数storedPermits大于该值时,说明系统冷下来了。此时需要进入预热阶段,加大生成单个令牌的耗时。当桶内存储的令牌数storedPermits小于该值时,说明进入热系统阶段,此时可以按正常速率生成令牌了。thresholdPermits默认是整个预热时间除以正常速率的一半。该值太小会过早进入预热阶段,影响性能。该值太大会对系统产生压力,没能达到预热效果。
上图中,横轴表示当前桶内库存令牌数,纵轴表示生成单个令牌的耗时。当桶内存储的令牌数大于storedPermits这个临界值时,系统就会进入预热阶段,对应的纵轴的生成单个令牌的耗时就会增加。当桶内存储的令牌数达到上限maxPermits时,系统处于最冷阶段,此时生成单个令牌的耗时就是最长,从而达到预热的目的。
(2)SmoothWarmingUp的初始化
在初始化SmoothWarmingUp的RateLimiter的create()方法中,会传入如下参数:
参数一:permitsPerSecond
表示的是稳定阶段的速率,也就是稳定阶段每秒生成的令牌数。
参数二:warmupPeriod
表示的是预热时间。
参数三:unit
表示的是预热时间warmupPeriod的单位。
参数四:coldFactor
表示的是冷却因子,这里固定是3.0,决定了coldIntervalMicros的值。
参数五:stopwatch
这可以理解成计时器,记录限流的计时信息,通过计时信息来计算令牌的产生和消耗等信息。
@Beta
@GwtIncompatible
@SuppressWarnings("GoodTime")
public abstract class RateLimiter {...//Creates a RateLimiter with the specified stable throughput, //given as "permits per second" (commonly referred to as QPS, queries per second), //and a warmup period, during which the RateLimiter smoothly ramps up its rate, //until it reaches its maximum rate at the end of the period (as long as there are enough requests to saturate it). //Similarly, if the RateLimiter is left unused for a duration of warmupPeriod, //it will gradually return to its "cold" state, //i.e. it will go through the same warming up process as when it was first created.//The returned RateLimiter is intended for cases where the resource that actually fulfills the requests (e.g., a remote server) needs "warmup" time, //rather than being immediately accessed at the stable (maximum) rate.//The returned RateLimiter starts in a "cold" state (i.e. the warmup period will follow), //and if it is left unused for long enough, it will return to that state.//创建一个具有指定稳定吞吐量的RateLimiter,//入参为:"每秒多少令牌"(通常称为QPS,每秒的查询量),以及平稳增加RateLimiter速率的预热期,//直到RateLimiter在该预热周期结束时达到最大速率(只要有足够的请求使其饱和);//类似地,如果RateLimiter在预热时段的持续时间内未被使用,它将逐渐返回到它的"冷"状态,//也就是说,它将经历与最初创建时相同的预热过程;//返回的RateLimiter适用于实际满足请求的资源(例如远程服务器)需要"预热"时间的情况,而不是以稳定(最大)速率立即访问;//返回的RateLimiter在"冷"状态下启动(也就是说,接下来将是预热期),如果它被闲置足够长的时间,它就会回到那个"冷"状态;//@param permitsPerSecond the rate of the returned RateLimiter, measured in how many permits become available per second//@param warmupPeriod the duration of the period where the RateLimiter ramps up its rate, before reaching its stable (maximum) rate//@param unit the time unit of the warmupPeriod argumentpublic static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit) {checkArgument(warmupPeriod >= 0, "warmupPeriod must not be negative: %s", warmupPeriod);return create(permitsPerSecond, warmupPeriod, unit, 3.0, SleepingStopwatch.createFromSystemTimer());}@VisibleForTestingstatic RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit, double coldFactor, SleepingStopwatch stopwatch) {RateLimiter rateLimiter = new SmoothWarmingUp(stopwatch, warmupPeriod, unit, coldFactor);//调用RateLimiter.setRate()方法rateLimiter.setRate(permitsPerSecond);return rateLimiter;}//Updates the stable rate of this RateLimiter, //that is, the permitsPerSecond argument provided in the factory method that constructed the RateLimiter. //Currently throttled threads will not be awakened as a result of this invocation, //thus they do not observe the new rate; only subsequent requests will.//Note though that, since each request repays (by waiting, if necessary) the cost of the previous request, //this means that the very next request after an invocation to setRate() will not be affected by the new rate; //it will pay the cost of the previous request, which is in terms of the previous rate.//The behavior of the RateLimiter is not modified in any other way, //e.g. if the RateLimiter was configured with a warmup period of 20 seconds, //it still has a warmup period of 20 seconds after this method invocation.//更新该RateLimiter的稳定速率,即在构造RateLimiter的工厂方法中提供permitsPerSecond参数;//当前被限流的线程将不会由于这个调用而被唤醒,因此它们没有观察到新的速率;只有随后的请求才会;//但是要注意的是,由于每个请求(如果需要,通过等待)会偿还先前请求的成本,//这意味着调用setRate()方法后的下一个请求将不会受到新速率的影响,//它将按照先前的速率处理先前请求的成本;//RateLimiter的行为不会以任何其他方式修改,//例如:如果RateLimiter被配置为具有20秒的预热周期,在该方法调用之后,它仍然有20秒的预热期;//@param permitsPerSecond the new stable rate of this {@code RateLimiter}public final void setRate(double permitsPerSecond) {checkArgument(permitsPerSecond > 0.0 && !Double.isNaN(permitsPerSecond), "rate must be positive");//在同步代码块中设定速率synchronized (mutex()) {//调用SmoothRateLimiter.doSetRate()方法doSetRate(permitsPerSecond, stopwatch.readMicros());}}...
}@GwtIncompatible
abstract class SmoothRateLimiter extends RateLimiter {//The currently stored permits. //令牌桶中当前缓存的未消耗的令牌数double storedPermits;//The maximum number of stored permits.//令牌桶中允许存放的最大令牌数double maxPermits;//The interval between two unit requests, at our stable rate.//E.g., a stable rate of 5 permits per second has a stable interval of 200ms.//按照我们稳定的速率,两个单位请求之间的时间间隔;例如,每秒5个令牌的稳定速率具有200ms的稳定间隔double stableIntervalMicros;//The time when the next request (no matter its size) will be granted. //After granting a request, this is pushed further in the future. Large requests push this further than small requests.//下一个请求(无论大小)将被批准的时间.//在批准请求后,这将在未来进一步推进,大请求比小请求更能推动这一进程。private long nextFreeTicketMicros = 0L;//could be either in the past or future...//这是一个可以重复调用的函数.//第一次调用和非第一次调用的过程有些不一样,目的是设定一个新的速率Rate.@Overridefinal void doSetRate(double permitsPerSecond, long nowMicros) {//调用SmoothRateLimiter.resync()方法,重试计算和同步存储的预分配的令牌.resync(nowMicros);//计算稳定的发放令牌的时间间隔. 单位us, 比如qps为5, 则为200ms即20万us的间隔进行令牌发放. double stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;this.stableIntervalMicros = stableIntervalMicros;//调用SmoothWarmingUp.doSetRate()设定其内部的比率.doSetRate(permitsPerSecond, stableIntervalMicros);}//Updates storedPermits and nextFreeTicketMicros based on the current time.//根据当前时间,更新storedPermits和nextFreeTicketMicros变量//注意: 在初始化SmoothBursty时会第一次调用resync()方法,此时各值的情况如下://coolDownIntervalMicros = 0、nextFreeTicketMicros = 0、newPermits = 无穷大.//maxPermits = 0(初始值,还没有重新计算)、最后得到的: storedPermits = 0;//同时,nextFreeTicketMicros = "起始时间"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;}}abstract void doSetRate(double permitsPerSecond, double stableIntervalMicros);...static final class SmoothWarmingUp extends SmoothRateLimiter {private final long warmupPeriodMicros;//The slope of the line from the stable interval (when permits == 0), to the cold interval (when permits == maxPermits)private double slope;//斜率private double thresholdPermits;private double coldFactor;SmoothWarmingUp(SleepingStopwatch stopwatch, long warmupPeriod, TimeUnit timeUnit, double coldFactor) {super(stopwatch);//将warmupPeriod转换成微妙并赋值给warmupPeriodMicrosthis.warmupPeriodMicros = timeUnit.toMicros(warmupPeriod);this.coldFactor = coldFactor;}@Overridevoid doSetRate(double permitsPerSecond, double stableIntervalMicros) {double oldMaxPermits = maxPermits;//stableIntervalMicros此时已由前面的SmoothRateLimiter.doSetRate()方法设为:1/qps//coldFactor的值默认会初始化为3//因此系统最冷时的令牌生成间隔:coldIntervalMicros等于3倍的普通间隔stableIntervalMicrosdouble coldIntervalMicros = stableIntervalMicros * coldFactor;//warmupPeriodMicros是用户传入的预热时间//stableIntervalMicros是稳定期间令牌发放的间隔//进入预热阶段的临界令牌数thresholdPermits,默认就是:整个预热时间除以正常速率的一半//该值太小会过早进入预热阶段,影响性能;该值太大会对系统产生压力,没达到预热效果thresholdPermits = 0.5 * warmupPeriodMicros / stableIntervalMicros;//最大令牌数maxPermits = thresholdPermits + 2.0 * warmupPeriodMicros / (stableIntervalMicros + coldIntervalMicros);//斜率slope = (coldIntervalMicros - stableIntervalMicros) / (maxPermits - thresholdPermits);//设置当前桶内的存储令牌数//突发型的RateLimiter——SmoothBursty://初始化时不会预生成令牌,因为storedPermits初始为0;//随着时间推移,则会产生新的令牌,这些令牌如果没有被消费,则会存储在storedPermits里;//预热型的RateLimiter——SmoothWarmingUp://初始化时会预生成令牌,并且初始化时肯定是系统最冷的时候,所以桶内默认就是maxPermitsif (oldMaxPermits == Double.POSITIVE_INFINITY) {//if we don't special-case this, we would get storedPermits == NaN, belowstoredPermits = 0.0;} else {//对于SmoothWarmingUp的RateLimiter来说,其初始存储值storedPermits是满的,也就是存储了最大限流的令牌数//而对于突发型的限流器SmoothBursty来说,其初始存储值storedPermits是0storedPermits = (oldMaxPermits == 0.0) ? maxPermits : storedPermits * maxPermits / oldMaxPermits;}}...}...
}
SmoothWarmingUp初始化时就是系统最冷的时候,此时令牌桶内的已存储令牌数等于maxPermits。SmoothWarmingUp的doSetRate()方法涉及的变量有:
变量一:stableIntervalMicros
表示的是稳定阶段生成令牌的速率,也就是1 / qps。
stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond
变量二:warmupPeriodMicros
表示的是根据构造方法中传入的预热阶段总时间warmupPeriod换算成的微秒值,将时间单位控制在微秒会让耗时更精确。
变量三:coldIntervalMicros
表示的是系统最冷时的令牌生成速率。
coldIntervalMicros = stableIntervalMicros * coldFactor
变量四:thresholdPermits
表示的是进入预热阶段的临界令牌数。
thresholdPermits = 0.5 * warmupPeriodMicros / stableIntervalMicros
变量五:maxPermits
表示的是令牌桶内的最大令牌数。
maxPermits = 稳定阶段生成的令牌数 + 预热阶段生成的令牌数
稳定阶段生成的令牌数是thresholdPermits,预热阶段的总时间是warmupPeriodMicros,所以预热阶段生成令牌的平均速率是:
(stableIntervalMicros + coldIntervalMicros) / 2
所以预热阶段生成的令牌数就是:
2.0 * warmupPeriodMicros / (stableIntervalMicros + coldIntervalMicros)
变量六:slope
表示的是斜率或者坡度,单位是微秒。预热阶段是以固定速度来提速的,预热阶段生成后一个令牌的耗时比生成上一个令牌的耗时要多slope微秒。也就是每生成一个令牌,下一个令牌的耗时就会固定增加slope微秒。已知预热阶段每个令牌的初始耗时为coldIntervalMicros微秒,预热结束时每个令牌的耗时为stableIntervalMicros微秒,整个预热阶段产生的令牌数是maxPermits - thresholdPermits,所以可以得出预热阶段生成令牌的增速为:
slope = (coldIntervalMicros - stableIntervalMicros) / (maxPermits - thresholdPermits)
变量七:storedPermits
默认SmoothWarmingUp初始化时就是系统最冷的时候,此时的storedPermits = maxPermits。
(3)SmoothWarmingUp中maxPermits的计算
一.计算公式分析
stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;
thresholdPermits = 0.5 * warmupPeriodMicros / stableIntervalMicros;
maxPermits = thresholdPermits + 2.0 * warmupPeriodMicros / (stableIntervalMicros + coldIntervalMicros);进行变换:
maxPermits - thresholdPermits = 2.0 * warmupPeriodMicros / (stableIntervalMicros + coldIntervalMicros)继续变换:
(stableIntervalMicros + coldIntervalMicros) * (maxPermits - thresholdPermits ) / 2 = warmupPeriodMicros其中梯形的斜边对应的斜率是:
slope = (coldIntervalMicros - stableIntervalMicros) / (maxPermits - thresholdPermits);
结合如下的图(令牌的发放时间间隔随着已存储的令牌不同而不同)可知:maxPermits - thresholdPermits就是梯形的高,stableIntervalMicros + coldIntervalMicros就是梯形的两个底的和。所以给定梯形的面积即warmupPeriodMicros,就可以计算出maxPermits。也就是根据传入的预热时间 + 稳定时的令牌发放间隔 + 冷却因子,就可以计算出预热期间能发放的最大令牌数。

二.举例说明
假如QPS限制为100,预热时间为5秒,那么:
stableIntervalMicros = 1s / 100 = 10ms
coldIntervalMicros = 10ms * 3 = 30ms
也就是说在预热期间,最慢会慢到到每30ms才产生一个令牌,预热周期是5000ms。由于梯形面积5000ms = (上底10ms + 下底30ms) * h / 2,那么h = 10000 / 40 = 250个令牌。
由如下的公式可得如下的结果:
公式:thresholdPermits = 0.5 * warmupPeriodMicros / stableIntervalMicros
结果:thresholdPermits = 0.5 * 预热周期5000ms / 稳定间隔10ms = 250
也就是说,在5s的预热周期内,按正常速率本来要生成500个令牌。但SmoothWarmingUp会以正常速率(每10ms一个令牌)生成其中一半,剩下一半再用5s的预热时间来进行预热式生成。
根据上面计算maxPermits的公式:
maxPermits = thresholdPermits + 2.0 * warmupPeriodMicros / (stableIntervalMicros + coldIntervalMicros);
因为冷却间隔时间是稳定间隔时间的3倍,所以:
stableIntervalMicros + coldIntervalMicros = 4stableIntervalMicros
因此该公式的后半部分也是:0.5倍 * 预热周期5000s / 稳定间隔10ms。
总结:带有预热型的限流器SmoothWarmingUp,会用一个预热周期的时间配合稳定间隔时间来确定最大可存储的令牌数。这个最大可存储的令牌的一半,是按照稳定的、正常速率生成的,另外一半令牌的平均生成速率是正常速率的一半。
(4)SmoothWarmingUp获取令牌
在调用SmoothWarmingUp的acquire()方法获取令牌时,最后会调用到SmoothRateLimiter的reserveEarliestAvailable()方法计算当前线程需要阻塞等待的时间,这个阻塞等待的时间由两部分组成。第一部分是从桶中获取storedPermitsToSpend个现有令牌的耗时,第二部分是等待生成freshPermits个新鲜令牌的耗时。
SmoothWarmingUp从桶中获取storedPermitsToSpend个现有令牌的耗时,会调用SmoothWarmingUp.storedPermitsToWaitTime()方法计算具体的耗时。该等待时间又会分为两部分进行计算:第一部分是获取预热阶段的令牌的耗时,第二部分是获取稳定阶段的令牌的耗时。并且只有当预热阶段的令牌获取完还不够时,才会去获取稳定阶段的令牌。
比如请求4个令牌,此时桶内令牌数是22,进入预热阶段的临界值是20。那么桶内稳定阶段生成的令牌就是20个,预热阶段生成的令牌就是2个。面对要获取4个令牌的请求,会先获取预热阶段的全部令牌也就是2个,然后再获取稳定阶段中的2个令牌。

获取预热阶段的令牌的耗时 = (初始速度 + 结束速度) * 令牌数 / 2
获取稳定阶段的令牌的耗时 = 固定速率stableIntervalMicros * 令牌数
@Beta
@GwtIncompatible
@SuppressWarnings("GoodTime")
public abstract class RateLimiter {...//无限等待的获取//Acquires the given number of permits from this RateLimiter, //blocking until the request can be granted. //Tells the amount of time slept, if any.//@param permits the number of permits to acquire,获取的令牌数量//@return time spent sleeping to enforce rate, in seconds; 0.0 if not rate-limited@CanIgnoreReturnValuepublic double acquire(int permits) {//调用RateLimiter.reserve()方法//预支令牌并获取需要阻塞的时间:即预定数量为permits的令牌数,并返回需要等待的时间long microsToWait = reserve(permits);//将需要等待的时间补齐, 从而满足限流的需求,即根据microsToWait来让线程sleep(共性)stopwatch.sleepMicrosUninterruptibly(microsToWait);//返回这次调用使用了多少时间给调用者return 1.0 * microsToWait / SECONDS.toMicros(1L);}//Reserves the given number of permits from this RateLimiter for future use, //returning the number of microseconds until the reservation can be consumed.//从这个RateLimiter限速器中保留给定数量的令牌,以备将来使用,返回可以使用保留前的微秒数//@return time in microseconds to wait until the resource can be acquired, never negativefinal long reserve(int permits) {checkPermits(permits);//由于涉及并发操作,所以必须使用synchronized进行互斥处理synchronized (mutex()) {//调用RateLimiter.reserveAndGetWaitLength()方法return reserveAndGetWaitLength(permits, stopwatch.readMicros());}}//Reserves next ticket and returns the wait time that the caller must wait for.//预定下一个ticket,并且返回需要等待的时间final long reserveAndGetWaitLength(int permits, long nowMicros) {//调用SmoothRateLimiter.reserveEarliestAvailable()方法long momentAvailable = reserveEarliestAvailable(permits, nowMicros);return max(momentAvailable - nowMicros, 0);}//Reserves the requested number of permits and returns the time that those permits can be used (with one caveat).//保留请求数量的令牌,并返回可以使用这些令牌的时间(有一个警告)//生产令牌、获取令牌、计算阻塞时间的具体细节由子类来实现//@return the time that the permits may be used, or, if the permits may be used immediately, an arbitrary past or present timeabstract long reserveEarliestAvailable(int permits, long nowMicros);...
}@GwtIncompatible
abstract class SmoothRateLimiter extends RateLimiter {//The currently stored permits. //令牌桶中当前缓存的未消耗的令牌数double storedPermits;//The maximum number of stored permits. //令牌桶中允许存放的最大令牌数double maxPermits;//The interval between two unit requests, at our stable rate.//E.g., a stable rate of 5 permits per second has a stable interval of 200ms.//按照我们稳定的速率,两个单位请求之间的时间间隔;例如,每秒5个令牌的稳定速率具有200ms的稳定间隔double stableIntervalMicros;//The time when the next request (no matter its size) will be granted. //After granting a request, this is pushed further in the future. Large requests push this further than small requests.//下一个请求(无论大小)将被批准的时间. 在批准请求后,这将在未来进一步推进,大请求比小请求更能推动这一进程.private long nextFreeTicketMicros = 0L;//could be either in the past or future...@Overridefinal long reserveEarliestAvailable(int requiredPermits, long nowMicros) {//1.根据nextFreeTicketMicros计算新产生的令牌数,更新当前未使用的令牌数storedPermits//获取令牌时调用SmoothRateLimiter.resync()方法与初始化时的调用不一样.//此时会把"没有过期"的令牌存储起来.//但是如果计数时间nextFreeTicketMicros是在未来. 那就不做任何处理.resync(nowMicros);//下一个请求(无论大小)将被批准的时间,这个值将被作为方法结果返回long returnValue = nextFreeTicketMicros;//2.计算需要阻塞等待的时间//2.1.先从桶中取未消耗的令牌,如果桶中令牌数不足,看最多能取多少个//存储的令牌可供消费的数量double storedPermitsToSpend = min(requiredPermits, this.storedPermits);//2.2.计算是否需要等待新鲜的令牌(当桶中现有的令牌数不足时就需要等待新鲜的令牌),如果需要,则计算需要等待的令牌数//需要等待的令牌:新鲜的令牌double freshPermits = requiredPermits - storedPermitsToSpend;//计算需要等待的时间//分两部分计算:waitMicros = 从桶中获取storedPermitsToSpend个现有令牌的代价 + 等待生成freshPermits个新鲜令牌的代价//从桶中取storedPermitsToSpend个现有令牌也是有代价的,storedPermitsToWaitTime()方法是个抽象方法,会由SmoothBursty和SmoothWarmingUp实现//对于SmoothBursty来说,storedPermitsToWaitTime()会返回0,表示已经存储的令牌不需要等待.//而生成新鲜令牌需要等待的代价是:新鲜令牌的个数freshPermits * 每个令牌的耗时stableIntervalMicroslong waitMicros = storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend) + (long) (freshPermits * stableIntervalMicros);//3.更新nextFreeTicketMicros//由于新鲜的令牌可能已被预消费,所以nextFreeTicketMicros就得往后移,以表示这段时间被预消费了this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);//4.扣减令牌数,更新桶内剩余令牌//最后把上面计算的可扣减的令牌数量从存储的令牌里减掉this.storedPermits -= storedPermitsToSpend;//返回请求需要等待的时间//需要注意returnValue被赋值的是上次的nextFreeTicketMicros,说明当前这次请求获取令牌的代价由下一个请求去支付return returnValue;}//Updates storedPermits and nextFreeTicketMicros based on the current time.//根据当前时间,更新storedPermits和nextFreeTicketMicros变量//计算nextFreeTicketMicros到当前时间内新产生的令牌数,这个就是延迟计算void resync(long nowMicros) {//if nextFreeTicket is in the past, resync to now//一般当前的时间是大于下个请求被批准的时间//此时:会把过去的时间换成令牌数存储起来,注意存储的令牌数不能大于最大的令牌数//当RateLimiter初始化好后,可能刚开始没有流量,或者是一段时间没有流量后突然来了流量//此时可以往"后"预存储一秒时间的令牌数. 也就是这里所说的burst能力//如果nextFreeTicketMicros在未来的一个时间点,那这个if判断便不满足//此时,不需要进行更新storedPermits和nextFreeTicketMicros变量//此种情况发生在:"预借"了令牌的时候if (nowMicros > nextFreeTicketMicros) {//时间差除以生成一个新鲜令牌的耗时,coolDownIntervalMicros()是抽象方法,由子类实现double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();//更新令牌桶内已存储的令牌个数,注意不超过最大限制storedPermits = min(maxPermits, storedPermits + newPermits);//更新nextFreeTicketMicros为当前时间nextFreeTicketMicros = nowMicros;}}//Translates a specified portion of our currently stored permits which we want to spend/acquire, into a throttling time.//Conceptually, this evaluates the integral of the underlying function we use, for the range of [(storedPermits - permitsToTake), storedPermits].//This always holds: 0 <= permitsToTake <= storedPermits//从桶中取出已存储的令牌的代价,由子类实现//这是一个抽象函数,SmoothBursty中的实现会直接返回0,可以认为已经预分配的令牌,在获取时不需要待待时间abstract long storedPermitsToWaitTime(double storedPermits, double permitsToTake);//Returns the number of microseconds during cool down that we have to wait to get a new permit.//每生成一个新鲜令牌的耗时,由子类实现abstract double coolDownIntervalMicros();...static final class SmoothWarmingUp extends SmoothRateLimiter {private final long warmupPeriodMicros;private double slope;//斜率private double thresholdPermits;private double coldFactor;...@Overridelong storedPermitsToWaitTime(double storedPermits, double permitsToTake) {//检查当前桶内存储的令牌数是否大于进入预热阶段的临界令牌数thresholdPermitsdouble availablePermitsAboveThreshold = storedPermits - thresholdPermits;long micros = 0;//如果当前桶内存储的令牌数大于进入预热阶段的临界令牌数thresholdPermits//则说明系统当前已经冷下来了,需要进入预热期,于是需要计算在预热期生成令牌的耗时if (availablePermitsAboveThreshold > 0.0) {//计算在超出临界值的令牌中需要取出多少个令牌,并计算耗时double permitsAboveThresholdToTake = min(availablePermitsAboveThreshold, permitsToTake);//计算预热阶段的耗时,前半部分的permitsToTime()计算的是生成令牌的初始速率,后半部分的permitsToTime()计算的是生成令牌的结束速率double length = permitsToTime(availablePermitsAboveThreshold) + permitsToTime(availablePermitsAboveThreshold - permitsAboveThresholdToTake);//总耗时 = ((初始速率 + 结束速率) * 令牌数) / 2micros = (long) (permitsAboveThresholdToTake * length / 2.0);permitsToTake -= permitsAboveThresholdToTake;}//加上稳定阶段的令牌耗时就是总耗时micros += (long) (stableIntervalMicros * permitsToTake);return micros;}//已知每生成一个令牌,下一个令牌的耗时就会固定增加slope微秒//那么在知道初始耗时stableIntervalMicros的情况下,就可以按如下公式求出生成第permits个令牌的耗时private double permitsToTime(double permits) {return stableIntervalMicros + permits * slope;}@Overridedouble coolDownIntervalMicros() {//预热时长 / 最大令牌数return warmupPeriodMicros / maxPermits;}}...
}
(5)SmoothBursty和SmoothWarmingUp的对比
SmoothBursty和SmoothWarmingUp这两种限流器都使用了预支令牌的思路,就是当前线程获取令牌的代价(阻塞时间)需要由下一个线程来支付。这样可以减少当前线程阻塞的概率,因为下一个请求不确定什么时候才来。如果下一个请求很久才来,那么这段时间产生的新令牌已经满足下一个线程的需求,这样就不用阻塞了。
一.在SmoothBursty中
桶内的已存储令牌是可以直接拿来用的,不需要额外的耗时,以此应对突发的流量,但这些已存储的令牌是之前低流量时积累下来的。
如果流量一直处于满负荷,没有结余的令牌,那么当突发流量到来时,仍然会被限流。
而且令牌桶内默认最大的令牌数就是1秒内产生的令牌。比如QPS设置为10的话,那么令牌桶内最多存储10个令牌。当QPS=20的流量到来时,也只够1秒钟的消耗,后面又会进入限流状态。
二.在SmoothWarmingUp中
桶内的已存储令牌是不可以直接拿来用的,需要额外的耗时。为了弥补SmoothBursty的不足,它将系统分为热系统和冷系统两个阶段。
满负荷流量或者突发流量对于热系统来说,可能危害不大。因为系统的线程池、缓存、连接池在热系统下都火力全开、抗压能力强。但对于冷系统,满负荷流量和突发流量会加大系统压力,导致各种问题。
所以一般会加入预热的思路来控制冷系统下的流量(即预热阶段等待时间会更长),而系统的冷热程度就是通过令牌桶内已存储的未消耗的令牌数来判断。因为当系统冷下来时,也就是系统流量小的时候,令牌消耗速度就会少,相应的令牌桶内已存储的令牌数就会多起来。
如果桶内的令牌数超过了进入预热阶段的临界令牌数thresholdPermits,那么就代表系统进入了预热阶段,在该阶段获取令牌的耗时将会增大,而且增大的速度是slope。
相关文章:
Sentinel源码—5.FlowSlot借鉴Guava的限流算法二
大纲 1.Guava提供的RateLimiter限流使用示例 2.Guava提供的RateLimiter简介与设计 3.继承RateLimiter的SmoothBursty源码 4.继承RateLimiter的SmoothWarmingUp源码 3.继承RateLimiter的SmoothBursty源码 (1)SmoothBursty的初始化流程 (2)SmoothBursty的初始化完成后的变量…...
下载electron 22.3.27 源码错误集锦
下载步骤同 electron源码下载及编译_electron源码编译-CSDN博客 问题1 从github 下载 dugite超时,原因没有找到 Validation failed. Expected 8ea2d0d3c9d9e4615069913207371ffe892dc10fb93975972f2f6e668f2e3b3a but got e3b0c44298fc1c149afbf4c8996fb92427ae41e…...
安卓的桌面 launcher是什么
安卓的桌面Launcher是一种安卓应用程序,它主要负责管理和展示手机主屏幕的界面以及相关功能,为用户提供与设备交互的主要入口。以下是其详细介绍: 功能 主屏幕管理:用户可以在主屏幕上添加、删除和排列各种应用程序图标、小部件…...
基础数学知识-线性代数
1. 矩阵相乘 c i j = a i k ∗ b k j c_{ij} = a_{ik} * b_{kj} cij=aik∗bkj 1. 范数 1. 向量的范数 任意一组向量设为 x ⃗ = ( x 1 , x 2 , . . . , x N ) \vec{x}=(x_1,x_2,...,x_N) x =(x1,x2,...,xN) 如下: 向量的1范数: 向量的各个元素的绝对值之和∥ …...
kubeadm极速部署Kubernetes 1.26.X 版本集群
1.1 Kubernetes 1.26版本集群部署环境准备 1.1.1 主机操作系统说明 序号操作系统及版本备注1CentOS7u9 1.1.2 主机硬件配置说明 需求CPU内存硬盘角色主机名值4C8G100GBmastermaster01值4C8G100GBworker(node)node01值4C8G100GBworker(node)node02 1.1.3 主机配置 1.1.3.1…...
重构未来智能:Anthropic 解码Agent设计哲学三重奏
第一章 智能体进化论:从工具到自主体的认知跃迁 1.1 LLM应用范式演进图谱 阶段技术形态应用特征代表场景初级阶段单功能模型硬编码规则执行文本摘要/分类进阶阶段工作流编排多模型协同调度跨语言翻译流水线高级阶段自主智能体动态决策交互编程调试/客服对话 1.1.…...
互联网大厂Java面试:微服务与分布式系统挑战
互联网大厂Java面试:微服务与分布式系统挑战 在互联网的大潮中,无数程序员怀揣着梦想,希望能在一线大厂找到自己的位置。今天的故事主角是马飞机,一位充满幽默感但技术略显水货的程序员。他来到了一家知名互联网公司参加Java开发…...
Gradle与Idea整合
文章目录 1. Groovy 简介2. Groovy 安装[非必须]3. 在idea中创建java工程 1. Groovy 简介 在某种程度上,Groovy可以被视为Java的一种脚本化改良版,Groovy也是运行在JVM上,它可以很好地与Java代码及其相关库进行交互操作。它是一种成熟的面向对象编程语言…...
基于springboot+vue的校园二手物品交易平台
开发语言:Java框架:springbootJDK版本:JDK1.8服务器:tomcat7数据库:mysql 5.7(一定要5.7版本)数据库工具:Navicat11开发软件:eclipse/myeclipse/ideaMaven包:…...
PoCL环境搭建
PoCL环境搭建 **一.关键功能与优势****二.设计目的****三.测试步骤**1.创建容器2.安装依赖3.编译安装pocl4.运行OpenCL测试程序 Portable Computing Language (PoCL) 简介 Portable Computing Language (PoCL) 是一个开源的、符合标准的异构计算框架,旨在为 OpenCL…...
【前端Skill】点击目标元素定位跳转IDE中的源代码
参考:https://juejin.cn/post/7326002010084311079 快手开源项目: https://github.com/zh-lx/code-inspector 目前在umi项目中用到 记录一下umi项目中如何使用 安装 npm i code-inspector-plugin -D --registryhttps://registry.npmmirror.com/ 配…...
OpenCV图像上加数字水印示例
OpenCV计算机视觉开发实践:基于Qt C - 商品搜索 - 京东 14.1 基本概念 当今,生成式人工智能(Artificial Intelligence Generated Content,AIGC)的火爆引燃了数字水印,说实话数字水印并不是一项新的技术&…...
Python爬虫从入门到实战详细版教程Char01:爬虫基础与核心技术
1.1 什么是网络爬虫? 1.1.1 定义与分类 网络爬虫:互联网世界的“信息捕手” 网络爬虫(Web Crawler),又称网络蜘蛛或网络机器人,是一种通过预设规则自动访问网页、提取数据的程序系统。从技术视角看,其核心任务是通过模拟浏览器行为向目标服务器发起请求,解析网页内容…...
使用blob文件流
1.后端 GetMapping(value "/static/**")public void view(HttpServletRequest request, HttpServletResponse response) {// ISO-8859-1 > UTF-8 进行编码转换String imgPath extractPathFromPattern(request);if(oConvertUtils.isEmpty(imgPath) || imgPath&q…...
Day-1 漏洞攻击实战
实训任务1 漏洞攻击实战一 使用 御剑 得到网站后台地址 数据库登录与日志配置 使用默认密码 root:root 登录phpMyAdmin,执行 SHOW VARIABLES LIKE general% 查看日志状态。 开启日志功能:set global general_log "ON";(配图&…...
AOSP Android14 Launcher3——RecentsView最近任务数据加载
最近任务是Launcher中的一个重要的功能,显示用户最近使用的应用,并可以快速切换到其中的应用;用户可以通过底部上滑停顿进入最近任务,也可以在第三方应用底部上滑进最近任务。 这两种场景之前的博客也介绍过,本文就不…...
基于深度学习的校园食堂菜品智能结算系统
校园食堂菜品智能结算系统说明文档 1. 系统概述 本系统是一款基于YOLO深度学习算法的校园食堂菜品智能结算平台,旨在通过计算机视觉技术实现食堂菜品的自动识别与结算,提高结算效率,减少人工成本,优化用户体验。系统采用PyQt5框…...
【UniApp】Vue2 scss 预编译器默认已由 node-sass 更换为 dart-sass
从 HBuilderX 4.56 ,vue2 项目也将默认使用 dart-sass 预编译器。 vue2开发者sass预处理注意: sass的预处理器,早年使用node-sass,也就是vue2最初默认的编译器。 sass官方推出了dart-sass来替代。node-sass已经停维很久了。 另…...
AI 硬件定制:开启智能新时代的钥匙
AI 硬件定制:开启智能新时代的钥匙 在科技飞速发展的当下,人工智能(AI)已不再是遥不可及的概念,它正以惊人的速度融入我们生活的方方面面。从智能手机中的语音助手,到工厂里的自动化生产线,AI 的身影无处不在。而在这股 AI 浪潮中,AI 硬件定制正逐渐崭露头角,成为推动…...
SpringBoot中配置文件的加载顺序
下面的优先级由高到低 命令行参数java系统属性java系统环境变量外部config文件夹的application-{profile}.ym文件外部的application-{profile}.ym文件内部config文件夹的application-{profile}.ym文件内部的application-{profile}.ym文件外部config文件夹的application.ym文件外…...
hooker frida版just_trust_me.js 2025升级 支持boringssl unpinning
曾几何时,我翻版了 Xposed 的 just_trust_me.apk, just_trust_me.js 脚本仿佛是一张通行证,让我们在 SSL Pinning 的高墙前轻松穿越。 但时代变了。BoringSSL、Cronet、静态 inline hook、动态 verify callback……一切都变得更加隐蔽和棘手…...
React Article模块
实现基础文章发布 安装富文本编辑器 使用useEffect钩子函数获取到channelList,对channelList函数进行一个遍历 渲染到option 实现表单校验 1给Form组件绑定onFinish()函数 拼接表单数据 上传封面 onChange函数获得的参数...
机器学习第二篇 多变量线性回归
数据集:世界幸福指数数据集中的变量有幸福指数排名、国家/地区、幸福指数得分、人均国内生产总值、健康预期寿命、自由权、社会支持、慷慨程度、清廉指数。我们选择GDP per Capita和Freedom,来预测幸福指数得分。 文件一:linear,…...
C语言对n进制的处理
先看一道题目: 从键盘获取一个正整数,如果把它转为16进制的数字,那么它是一个几位数呢?如果把它转为28进制又是一个几位数呢? 在讲这个题目之前,我们先要了解进制转换 什么是进制转换? 简单来说,进制就是数位的表示方法。 十进制(常用&am…...
【EasyPan】文件上传、文件秒传、文件转码、文件合并、异步转码、视频切割分析
【EasyPan】项目常见问题解答(自用&持续更新中…)汇总版 文件上传方法解析 一、方法总览 Transactional(rollbackFor Exception.class) public UploadResultDto uploadFile(...)核心能力: 秒传验证:通过MD5文件大小实现文…...
Ubuntu数据连接访问崩溃问题
目录 一、分析问题 1、崩溃问题本地调试gdb调试: 二、解决问题 1. 停止 MySQL 服务 2. 卸载 MySQL 相关包 3. 删除 MySQL 数据目录 4. 清理依赖和缓存 5.重新安装mysql数据库 6.创建程序需要的数据库 三、验证 1、动态库更新了 2、头文件更新了 3、重新…...
Oracle DBA 高效运维指南:高频实用 SQL 大全
大家好,这里是 DBA学习之路,专注于提升数据库运维效率。 目录 前言Top SQL表空间使用率RMAN 备份DataGuard等待事件行级锁在线日志切换用户信息ASM 磁盘组DBLink数据文件收缩AWR 写在最后 前言 作为一名 Oracle DBA,在日常数据库运维工作中&…...
【xlog日志文件】怎么删除里面包含某些字符串的行(使用excel)
将log日志,复制到单独一行 B列(可能一行很长,所以将整合后的放在A列) 使用公式可以筛选出 包含某些字符串的行 为true,将这些行直接删除 IF(COUNT(FIND("MediaMuxterThreadRussia",B2,1))>0,"包含",&quo…...
Spark-Streaming简介和核心编程
Spark-Streaming简介 概述:用于流式数据处理,支持Kafka、Flume等多种数据输入源,可使用Spark原语运算,结果能保存到HDFS、数据库等。它以DStream(离散化流)为抽象表示,是RDD在实时场景的封装&am…...
Docker 快速入门教程
1. Docker 基本概念 镜像(Image): 只读模板,包含创建容器的指令 容器(Container): 镜像的运行实例 Dockerfile: 用于构建镜像的文本文件 仓库(Repository): 存放镜像的地方(如Docker Hub) 2. 安装Docker 根据你的操作系统选择安装方式:…...
