当前位置: 首页 > news >正文

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的初始化完成后的变量…...

自由学习记录(56)

从贴图空间&#xff08;texture space&#xff09;将值还原到切线空间&#xff08;tangent space&#xff09;向量 tangentNormal.xy (packedNormal.xy * 2 - 1) * _BumpScale; 背后的知识点&#xff1a;法线贴图中的 RGB 是在 0~1 范围内编码的向量 所以贴图法线是怎么“压…...

计算机网络八股——HTTP协议与HTTPS协议

前言&#xff1a; 到时候我想要写一篇文章就是&#xff1a;在浏览器中输入URL并按下回车会发生什么&#xff1f; 然后将几篇文章全部串联到一起&#xff0c;现在几天的任务就是将这里的每个小部分进行一个详细的介绍 HTTP1.1简述与特性 Web 上的通信都是建⽴在 HTTP 协议上的…...

JAVAEE(网络原理—UDP报头结构)

我们本篇文章要讲的是UDP的报头结构以及注意事项。 下面呢&#xff0c;我先说一下UDP是什么&#xff1f; 1.UDP是什么&#xff1f; UDP是一种网络协议。网络协议是计算机网络中&#xff0c;为了使不同设备之间能够准确、高效地进行数据交换和通信&#xff0c;而预先制定的一…...

Redis-分布式锁

Redis-分布式锁 文章目录 Redis-分布式锁1.基本原理和不同方式实现方式对比2.Redis分布式锁的基本实现思路3.分布式锁误删问题一4.分布式锁误删问题二5.Redission1.功能介绍2.快速入门3.可重入锁原理4.锁重试和WatchDog机制1.锁重试2. WatchDog 机制&#xff08;锁自动续期&…...

如何优雅地为 Axios 配置失败重试与最大尝试次数

在 Vue 3 中&#xff0c;除了使用自定义的 useRequest 钩子函数外&#xff0c;还可以通过 axios 的拦截器 或 axios-retry 插件实现接口请求失败后的重试逻辑。以下是两种具体方案的实现方式&#xff1a; 方案一&#xff1a;使用 axios 拦截器实现重试 实现步骤&#xff1a; 通…...

Windows使用SonarQube时启动脚本自动关闭

一、解决的问题 Windows使用SonarQube时启动脚本自动关闭&#xff0c;并发生报错&#xff1a; ERROR: Elasticsearch did not exit normally - check the logs at E:\Inori_Code\Year3\SE\sonarqube-25.2.0.102705\sonarqube-25.2.0.102705\logs\sonarqube.log ERROR: Elastic…...

MYSQL初阶(暂为自用草稿)

目录 基本操作 database操作 table操作 数据类型 INT类型 bit类型 FLOAT类型 CHAR类型 DATE类型 SEL类型 表的约束 列约束 NULL DEFAULT PRIMARY KEY UNIQUE KEY 表约束 PRIMARY KEY FOREIGN KEY 其他补充 AUTO_INCREMENT COMMENT ZEROFILL 表的CRUD …...

交换排序——快速排序

交换排序的基本思路&#xff1a;把序列中的两个元素进行比较&#xff0c;根据需求对两个元素进行交换。特点是较大的元素向序列的尾部移动&#xff0c;较小的元素向序列的前部移动。 hoare法 在序列中任取一个元素作为基准值&#xff0c;一趟排序完成之后&#xff0c;以基准值为…...

资源-又在网上淘到金了

前言&#xff1a; 本期再分享网上冲浪发现的特效/动画/视频资源网站。 一、基本介绍&#xff1a; mantissa.xyz&#xff0c;about作者介绍为&#xff1a;Midge “Mantissa” Sinnaeve &#xff08;米奇辛纳夫&#xff09;是一位屡获殊荣的艺术家和导演&#xff0c;提供动画、…...

CSS中的`transform-style`属性:3D变换的秘密武器

在CSS中&#xff0c;当我们尝试创建复杂的3D场景时&#xff0c;transform-style属性变得尤为重要。它决定了子元素是在3D空间中呈现还是被展平到2D平面中。本文将深入探讨transform-style的用法&#xff0c;并通过具体的代码示例来展示如何利用这个属性来增强你的网页设计。 什…...

Step文件无法编辑怎么办?

Step文件无法编辑怎么办&#xff1f; 这里介绍两种方法&#xff0c; 1、 直接导入 准备step文件&#xff0c;solidworks导入后是这样&#xff0c;不能在上面直接编辑 图 1 点击右键&#xff0c;选择解除特征&#xff08;不同版本的可能不太一样&#xff0c;这里是solidworks2…...

从 LabelImg 到 Label Studio!AI 数据标注神器升级,Web 版真香

视频讲解&#xff1a; 从 LabelImg 到 Label Studio&#xff01;AI 数据标注神器升级&#xff0c;Web 版真香 Label Studio 支持图像、文本、音频、视频、时间序列等多类型数据标注&#xff0c;覆盖计算机视觉&#xff08;目标检测、语义分割&#xff09;、自然语言处理&#x…...

纯FPGA实现驱动AD9361配置的思路和实现之一 概述

我们在做ZYNQ系统开发时候做的IP基本都是AXI_LITE_SLAVE&#xff0c;是SLAVE&#xff0c;从设备。就是提供了若干寄存器接口供MASTER进行读写。SLAVE里面的逻辑通过读写动作或者读写的数据进行响应的动作。这种方式的好处是硬件层面可以访问寄存器&#xff0c;软件层面是可以实…...

Nacos配置中心服务端源码解析

文章目录 概述一、配置持久化到数据库二、发布事件2.1、事件发布者端2.1.1、DefaultPublisher#publish2.1.2、DefaultPublisher#run2.1.3、DefaultPublisher#receiveEvent 2.2、事件订阅者端2.2.1、Subscriber#onEvent2.2.2、ConfigCacheService#dump 总结&#xff1a;Nacos 配…...

SAP系统工艺路线的分配物料出现旧版包材

问题:工艺路线的物料错了 这是3月份技术部发现的问题,10000209这个成品有两个版本的BOM, 在创建新版的工艺路线里,发现分配的物料仍然是旧版的物料. 原因排查: 1 BOM中物料错误? 2 选错了生产版本,选了版本1? 3 生产版本设置中的可选BOM错误? 解决&#xff1a;把可选的BOM…...

JVM虚拟机--JVM的组成

(一)JVM的组成 一、JVM介绍 &#xff08;1&#xff09;JVM的作用 我们知道&#xff0c;Java代码要想在计算机中正常运行&#xff0c;就需要经过编译为class二进制字节码文件&#xff0c;而JVM就提供了class二进制字节码的运行环境。 一次编写&#xff0c;到处运行 因为JVM是…...

科学研究:怎么做

科研&#xff08;科学研究&#xff09;​​ 是指通过系统化的方法&#xff0c;探索自然、社会或人文领域的未知问题&#xff0c;以发现新知识、验证理论或解决实际问题的活动。它的核心是​​基于证据的探索与创新​​&#xff0c;旨在推动人类认知和技术的进步。 科研的核心要…...

PyTorch数据操作基础教程:从张量创建到高级运算

本文通过示例代码全面讲解PyTorch中张量的基本操作&#xff0c;包含创建、运算、广播机制、索引切片等核心功能&#xff0c;并提供完整的代码和输出结果。 1. 张量创建与基本属性 import torch# 创建连续数值张量 x torch.arange(12, dtypetorch.float32) print("原始张…...

微服务治理与可观测性

服务注册与发现 核心功能 服务实例动态变化&#xff1a;实例可能因扩缩容、故障或迁移导致IP变动。服务依赖解耦&#xff1a;调用方无需硬编码服务地址&#xff0c;降低耦合度。负载均衡&#xff1a;自动选择健康实例&#xff0c;提升系统可用性。 核心组件 服务注册中心&am…...

如何对docker镜像存在的gosu安全漏洞进行修复——筑梦之路

这里以mysql的官方镜像为例进行说明&#xff0c;主要流程为&#xff1a; 1. 分析镜像存在的安全漏洞具体是什么 2. 根据分析结果有针对性地进行修复处理 3. 基于当前镜像进行修复安全漏洞并复核验证 # 镜像地址mysql:8.0.42 安全漏洞现状分析 dockerhub网站上获取该镜像的…...

OpenCV 04.19 练习

1. 创建一个 PyQt 应用程序&#xff0c;该应用程序能够&#xff1a; 1.使用 OpenCV 加载一张图像。 2.在 PyQt 的窗口中显示这张图像。 3.提供四个按钮&#xff08;QPushButton&#xff09;&#xff1a; - 一个用于将图像转换为灰度图 - 一个用于将图像恢复为原始彩色图 - 一个…...

uv:重新定义Python开发效率的下一代工具链

在Python生态系统中,包管理和项目工具链的复杂性一直是开发者面临的一大挑战。从依赖管理、虚拟环境创建到多版本Python切换,传统的工具链(如pip、virtualenv、poetry等)虽然功能强大,但操作繁琐、性能不足的问题长期存在。而uv的出现,以颠覆性的速度和功能集成,为Pytho…...

【Easylive】​​Gateway模块 bootstrap.yml 解析

【Easylive】项目常见问题解答&#xff08;自用&持续更新中…&#xff09; 汇总版 Gateway模块 bootstrap.yml 常规解析 该配置文件定义了 Spring Cloud Gateway 的核心配置&#xff0c;包括 环境配置、服务注册、动态路由规则 等。以下是逐项解析&#xff1a; 1. 基础配…...

Warcraft Logs [Classic] [WCL] Usage Wizard <HTOC>

‌HTOC&#xff08;十字军的试炼&#xff09;副本中各个BOSS的ID如下‌&#xff1a; ‌629 - 诺森德野兽‌ ‌633 - 加拉克苏斯大王‌ ‌637 - 派系冠军‌ ‌641 - 瓦格里双子‌ ‌645 - 阿努巴拉克‌ encounterID!637 and encounterID!641 encounterID NOT IN (637,641) 伤害 …...

多模态大语言模型arxiv论文略读(二十八)

MM-SAP: A Comprehensive Benchmark for Assessing Self-Awareness of Multimodal Large Language Models in Perception ➡️ 论文标题&#xff1a;MM-SAP: A Comprehensive Benchmark for Assessing Self-Awareness of Multimodal Large Language Models in Perception ➡️…...

JavaScript数据类型简介

在JavaScript中&#xff0c;理解不同的数据类型是掌握这门语言的基础。数据类型决定了变量可以存储什么样的值以及这些值能够执行的操作。JavaScript支持多种数据类型&#xff0c;每种都有其特定的用途和特点。本文将详细介绍JavaScript中的主要数据类型&#xff0c;并提供一些…...

CasualLanguage Model和Seq2Seq模型的区别

**问题1&#xff1a;**Causal Language Modeling 和 Conditional Generation 、Sequence Classification 的区别是什么&#xff1f; 因果语言模型(Causal Language Model)&#xff1a; 预测给定文本序列中的下一个字符&#xff0c;一般用于文本生成、补全句子等&#xff0c;模型…...

在Qt和OSG中动态改变部分3D模型数据

要在Qt和OSG环境中导入3D模型并只对部分数据进行动态改变,你可以采用以下方法: 基本实现步骤 加载模型:使用OSG的读取器加载3D模型文件 访问特定部分:识别并获取模型中需要修改的部分 动态修改:在Qt界面或逻辑中设置修改这些部分的机制 更新显示:确保修改后的模型能够实…...

命令update-alternatives

❯ which pip /home/ying/anaconda3/bin/pipying192 ~ [2]> which pip /usr/bin/pip使用update-alternatives对他们进行管理和切换 快捷方式 和 实际路径不可以相同 所以我这边选择了/usr/local/bin目录作为介质存储快捷方式&#xff0c;另外该快捷方式会自己创建我们只需选…...

10.thinkphp的响应

响应输出 响应操作 1. 响应输出&#xff0c;有好几种&#xff1a;包括return、json()和view()等等&#xff1b; 2. 默认输出方式是以html格式输出&#xff0c;如果你发起json请求&#xff0c;则输出json&#xff1b; 3. 而背后是response对象&#xff0c;可以用response()输…...

【技术派后端篇】技术派中的白名单机制:基于Redis的Set实现

在技术派社区中&#xff0c;为了保证文章的质量和社区的良性发展&#xff0c;所有发布的文章都需要经过审核。然而&#xff0c;并非所有作者的文章都需要审核&#xff0c;我们通过白名单机制来优化这一流程。本文将详细介绍技术派中白名单的实现方式&#xff0c;以及如何利用Re…...

Keil A51汇编伪指令

以下是 Keil A51 汇编器支持的常用伪指令 及其详细说明&#xff0c;涵盖代码结构、数据定义、条件编译等关键功能&#xff0c;结合实际应用场景进行分类和示例&#xff1a; 一、程序结构与地址控制 伪指令功能语法示例说明ORG设置代码/数据起始地址ORG 0000H后续代码从指定地址…...

Windows上安装FFmpeg的详细指南

1.下载FFmpeg 访问FFmpeg官方下载页面&#xff1a;https://ffmpeg.org/download.html 点击"Windows builds from gyan.dev"或"Windows builds by BtbN" gyan.dev版本&#xff1a;https://www.gyan.dev/ffmpeg/builds/ BtbN版本&#xff1a;https://githu…...

jmeter利用csv进行参数化和自动断言

1.测试数据 csv测试数据如下&#xff08;以注册接口为例&#xff09; 2.jemer参数化csv设置 打开 jmeter&#xff0c;添加好线程组、HTTP信息头管理器、CSV 数据文件设置、注册请求、响应断言、查看结果树 1&#xff09; CSV 数据文件设置 若 CSV 中数据包含中文&#xff0c;…...

《Android 应用开发基础教程》——第二章:Activity 与生命周期详解

目录 第二章&#xff1a;Activity 与生命周期详解 2.1 什么是 Activity&#xff1f; 作用&#xff1a; 2.2 创建一个 Activity 示例代码 Manifest 注册&#xff1a; 2.3 Activity 生命周期&#xff08;Life Cycle&#xff09; 生命周期图解&#xff1a; 2.4 生命周期代…...

[Java]反射、String类补充

目录 1、反射定义 2、用途(了解) 3、反射相关的类 4、Class类(反射机制的起源) 4.1、相关方法 5、反射示例 5.1、获取Class对象 5.2、反射的使用 6、反射优点和缺点 7、String类补充 7.1、创建对象的思考 8、字符串常量池 9、再谈String对象创建 10、intern方法 …...

word表格批量操作——宏

word中所有表格代码 这个是表格的模板代码 Sub 表格通用代码() For i ActiveDocument.Tables.Count To 1 Step -1ActiveDocument.Tables (i) Next End Sub1、根据内容自动调整表格 Sub 表格适用内容() For i ActiveDocument.Tables.Count To 1 Step -1ActiveDocument.Tabl…...

eSTK.me

eSTK.me 调用 USIM 卡的 Applet 实现在 iOS 内自助切换 Profile。写卡仍然需要借助硬件读卡器或者兼容 eSIM 的 Android 手机。支持国行 iPhone。 eSTK.me&#xff08;固件 v2.x 及以后的版本&#xff09;基于 ETSI 的 Bearer Independent Protocol (BIP) 协议&#xff0c;使…...

四级英语备考指南

一、引言 大学英语四级考试是对大学生英语综合能力的一次重要检验。无论是为了学业要求&#xff0c;还是提升自身竞争力&#xff0c;顺利通过四级考试都有着重要意义。本文将为大家详细介绍四级英语的备考策略&#xff0c;帮助大家高效备考&#xff0c;取得理想成绩。 二、了…...

Java Web 之 互联网协议 100问

HTTP是什么&#xff1f; HTTP&#xff08;HyperText Transfer Protocol&#xff0c;超文本传输协议&#xff09;是互联网上应用最广泛的协议之一&#xff0c;用于在客户端&#xff08;如浏览器&#xff09;和服务器之间传输数据。它是 Web 通信的基础&#xff0c;支持浏览器访…...

【单倍型理解及计算系列之二】单倍型基本概念以及其与遗传定位中Bin的定义区别

问题&#xff1a;如何理解单倍型&#xff0c;与遗传定位中Bin的定义区别&#xff1a; 简而言之&#xff1a;就是单倍型是基于LD&#xff0c;通常为连锁不平衡&#xff08;LD&#xff09;较高的区域形成。但bin是人为划分的&#xff0c;如以固定SNP数量/固定长度设置&#xff0…...

ArcPy Mapping 模块基础

在地理信息系统&#xff08;GIS&#xff09;的应用中&#xff0c;地图制作是一个非常重要的环节。无论是生成用于展示的静态地图&#xff0c;还是动态更新的地图服务&#xff0c;都需要高效且灵活的工具来实现。ArcPy 提供了强大的mapping模块&#xff0c;可以帮助用户自动化地…...

AcWing 11:背包问题求方案数 ← 0-1背包

【题目来源】 https://www.acwing.com/problem/content/11/ 【题目描述】 有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。 第 i 件物品的体积是 vi&#xff0c;价值是 wi。 求解将哪些物品装入背包&#xff0c;可使这些物品的总体积不超过背包容量&#xff0c;且总…...

Redis增删改查

### 进入redis控制台 redis-cli --raw #加上raw,防止中文乱码### 增 127.0.0.1:6379> LPUSH list0 "hello" #增加一个list 1 127.0.0.1:6379> LRANGE list0 0 -1 #查看list hello### 删 127.0.0.1:6379> DEL list0 #删除list 1 127.0.0.1:6379> LRANG…...

多道程序和多任务操作系统区别

多道程序 vs. 多道任务&#xff1a;对比分析 ✅ 共同点 方面共同特征核心机制都依赖于进程/任务切换执行需求实现多个程序或任务"并发"执行系统支持都需要操作系统的支持&#xff08;如调度算法、内存管理&#xff09;本质目标提高资源利用率&#xff08;CPU不空转…...

【MySQL】MySQL建立索引不知道注意什么?

基本原则&#xff1a; 1.选择性原则&#xff1a; 选择高选择性的列建立索引(该列有大量不同的值) 2.适度原则&#xff1a;不是越多越好&#xff0c;每个索引都会增加写入开销 列选择注意事项&#xff1a; 1.常用查询条件列&#xff1a;WHERE字句中频繁使用的列 2.连接操作列…...

区块链木材业务服务平台:商贸物流新变革

区块链木材业务服务平台&#xff1a;商贸物流新变革 在全球商贸物流行业不断发展的当下&#xff0c;木材贸易作为其中重要的一环&#xff0c;面临着诸多挑战。区块链木材业务服务平台的出现&#xff0c;为木材商贸物流领域带来了全新的解决方案&#xff0c;正逐步引领行业走向…...

【AI提示词】经济学家

提示说明 经济学家致力于提供深入的经济分析和预测&#xff0c;帮助用户理解经济趋势、政策影响以及市场动态。他们通过专业的经济模型和数据分析&#xff0c;为用户在投资、决策等方面提供指导。 提示词 # 角色 经济学家## 注意 1. 经济学家专家需要具备深入分析经济现象的…...

C++用于保留浮点数的两位小数,使用宏定义方法(可兼容低版本Visual Studio)

文章目录 一、 描述二、 样例二、 结果输出 一、 描述 这个宏定义&#xff08;可放入.h头文件里&#xff09;使用基本的数学运算&#xff0c;几乎兼容所有版本的VS&#xff0c;以下可对正数做四舍五入&#xff1a; #define ROUND_TO_TWO(x) ( (floor((x) * 100 0.5) / 100) …...