Sentinel源码—9.限流算法的实现对比二
大纲
1.漏桶算法的实现对比
(1)普通思路的漏桶算法实现
(2)节省线程的漏桶算法实现
(3)Sentinel中的漏桶算法实现
(4)Sentinel中的漏桶算法与普通漏桶算法的区别
(5)Sentinel中的漏桶算法存在的问题
2.令牌桶算法的实现对比
(1)普通思路的令牌桶算法实现
(2)节省线程的令牌桶算法实现
(3)Guava中的令牌桶算法实现
(4)Sentinel中的令牌桶算法实现
(5)Sentinel中的令牌桶算法总结
三.SmoothWarmingUp的初始化
@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的acquire()方法
@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;}}...
}
(4)Sentinel中的令牌桶算法实现
一.WarmUpController的初始化
二.WarmUpController.canPass()方法
三.WarmUpController.syncToken()方法
四.WarmUpController.coolDownTokens()方法
Guava中的预热是通过控制令牌的生成时间来实现的,Sentinel中的预热则是通过控制每秒通过的请求数来实现的。在Guava中,冷却因子coldFactor固定为3,已被写死。在Sentinel中,冷却因子coldFactor默认为3,可通过参数修改。
一.WarmUpController的初始化
public class WarmUpController implements TrafficShapingController {//count是QPS阈值,即FlowRule中设定的阈值,表示系统在稳定阶段下允许的最大QPS//在预热阶段,系统允许的QPS不会直接到达count值,而是会逐渐增加(对应预热模型图从右向左),直到达到这个count值为止//这样就能实现让系统接收到的流量是一个平滑上升的状态,而不是让系统瞬间被打满protected double count;//coldFactor是冷却因子,表示系统在最冷时(预热阶段刚开始时)允许的QPS阈值与稳定阶段下允许的QPS阈值之比//此参数直接影响预热阶段允许的QPS递增值,冷却因子越大,预热阶段允许的QPS递增值越低,默认为3private int coldFactor;//告警值,大于告警值系统就进入预热阶段,小于告警值系统进入稳定阶段protected int warningToken = 0;//令牌桶可以存储的最大令牌数private int maxToken;//斜率,预热阶段令牌生成速率的增速protected double slope;//令牌桶中已存储的令牌数protected AtomicLong storedTokens = new AtomicLong(0);//最后一次添加令牌的时间戳protected AtomicLong lastFilledTime = new AtomicLong(0);public WarmUpController(double count, int warmUpPeriodInSec, int coldFactor) {construct(count, warmUpPeriodInSec, coldFactor);}public WarmUpController(double count, int warmUpPeriodInSec) {//warmUpPeriodInSec是预热时长,表示系统需要多长时间从预热阶段到稳定阶段//比如限制QPS为100,设置预热时长为10s,那么在预热阶段,令牌生成的速率会越来越快//可能第1s只允许10个请求通过,第2s可能允许15个请求通过,这样逐步递增,直至递增到100为止construct(count, warmUpPeriodInSec, 3);}private void construct(double count, int warmUpPeriodInSec, int coldFactor) {if (coldFactor <= 1) {throw new IllegalArgumentException("Cold factor should be larger than 1");}this.count = count;this.coldFactor = coldFactor;//thresholdPermits = 0.5 * warmupPeriodMicros / stableIntervalMicros;//1.告警值,大于告警值系统就进入预热阶段;例如预热时长为5s,QPS为100,那么warningToken就为250warningToken = (int)(warmUpPeriodInSec * count) / (coldFactor - 1);//maxPermits = thresholdPermits + 2 * warmupPeriodMicros / (stableIntervalMicros + coldIntervalMicros);//2.系统最冷时桶内存储的令牌数,例如预热时长为5s,QPS为100,那么maxToken为500maxToken = warningToken + (int)(2 * warmUpPeriodInSec * count / (1.0 + coldFactor));//slope = (coldIntervalMicros - stableIntervalMicros) / (maxPermits - thresholdPermits);//3.slope斜率,例如预热时长为5s,QPS为100,那么slope为0.00008slope = (coldFactor - 1.0) / count / (maxToken - warningToken);}...
}
二.WarmUpController.canPass()方法
步骤一:调用WarmUpController的syncToken()方法生成令牌并同步到令牌桶内
步骤二:判断令牌桶内剩余令牌数是否大于告警值
情况一:如果剩余令牌数大于警戒值,说明系统处于预热阶段,此时需要进一步比较令牌的生产速率与令牌的消耗速率。若消耗速率大,则限流,否则请求正常通行。
情况二:如果剩余令牌数小于警戒值,说明系统处于稳定阶段。此时就直接判断当前请求的QPS与阈值大小,超过阈值则限流。
三.WarmUpController.syncToken()方法
该方法会生成令牌并同步到令牌桶内。其中入参passQps是前一个时间窗口的QPS,即上一秒通过的QPS数。首先验证当前时间与最后更新时间,避免在同一时间窗口重复添加令牌。其次通过WarmUpController的coolDownTokens()方法获取最新的令牌数,接着利用CAS来保证更新令牌桶的线程安全性,最后通过减去上一秒通过的QPS数得到目前令牌桶剩余的令牌数来更新。
四.WarmUpController.coolDownTokens()方法
该方法会根据当前时间和上一个时间窗口通过的QPS计算更新后的令牌数。具体来说就是,首先获取当前令牌桶已存储的令牌数,然后判断桶内令牌数和告警值的大小。
情况一:如果令牌桶中已存储的令牌数小于告警值
说明系统已结束冷启动,即退出预热阶段进入了稳定阶段。也就是桶内已存储的令牌数没有达到进入预热阶段的阈值,此时需要较快地向令牌桶中添加令牌。
情况二:如果令牌桶中已存储的令牌数大于告警值
说明系统处于预热阶段,还在进行冷启动。此时如果上一个时间窗口通过的QPS,小于系统最冷时允许通过的QPS。那么就说明当前系统的负载比较低,可以向令牌桶中添加令牌。系统最冷时允许通过的QPS = (1 / (1 / count * coldFactor))。
其中,向令牌桶中添加令牌的处理,就是在当前令牌数量的基础上,加上从上次添加令牌到现在经过的时间乘以QPS阈值。
注意:Guava中的预热是通过控制令牌的生成时间来实现的,Sentinel中的预热是通过控制每秒通过的请求数来实现的。
Guava的实现侧重于调整请求间隔,这类似于漏桶算法。而Sentinel更注重控制每秒传入请求的数量,而不计算其间隔,这类似于令牌桶算法。
//The principle idea comes from Guava.
//However, the calculation of Guava is rate-based, which means that we need to translate rate to QPS.
//这个原理来自于Guava;
//然而,Guava的计算是基于速率的,这意味着我们需要将速率转换为QPS;//Requests arriving at the pulse may drag down long idle systems even though it has a much larger handling capability in stable period.
//It usually happens in scenarios that require extra time for initialization,
//e.g. DB establishes a connection, connects to a remote service, and so on.
//That's why we need "warm up".
//突发式的流量可能会拖累一个长期空闲的系统,即使这个系统在稳定阶段具有更大的流量处理能力;
//这通常发生在需要额外时间进行初始化的场景中,比如DB建立连接、连接到远程服务等;
//这就是为什么我们需要对系统进行"预热";//Sentinel's "warm-up" implementation is based on the Guava's algorithm.
//However, Guava’s implementation focuses on adjusting the request interval, which is similar to leaky bucket.
//Sentinel pays more attention to controlling the count of incoming requests per second without calculating its interval,
//which resembles token bucket algorithm.
//Sentinel的"预热"实现是基于Guava的算法的;
//然而,Guava的实现侧重于调整请求间隔,这类似于漏桶;
//而Sentinel更注重控制每秒传入请求的数量,而不计算其间隔,这类似于令牌桶算法;//The remaining tokens in the bucket is used to measure the system utility.
//Suppose a system can handle b requests per second.
//Every second b tokens will be added into the bucket until the bucket is full.
//And when system processes a request, it takes a token from the bucket.
//The more tokens left in the bucket, the lower the utilization of the system;
//when the token in the token bucket is above a certain threshold,
//we call it in a "saturation" state.
//桶中存储的令牌是用来测量系统的实用程序的;
//假设一个系统每秒可以处理b个请求;
//那么每秒就有b个令牌被添加到桶中,直到桶满为止;
//当系统处理一个请求时,就会从桶中获取一个令牌;
//桶中存储的令牌剩余得越多,那么就说明系统的利用率就越低;
//当令牌桶中的令牌数高于某个阈值时,我们称之为"饱和"状态;//Base on Guava’s theory, there is a linear equation we can write this in the form
//y = m * x + b where y (a.k.a y(x)), or qps(q)),
//is our expected QPS given a saturated period (e.g. 3 minutes in),
//m is the rate of change from our cold (minimum) rate to our stable (maximum) rate,
//x (or q) is the occupied token.
//根据Guava的理论,有一个线性方程,我们可以把它写成y = m * x + b;
//这是在给定饱和周期(例如3分钟)的情况下预期的QPS;
//m是从我们的冷(最小)速率到我们的稳定(最大)速率的变化率;
//x(或q)就是需要被占用的令牌数;
public class WarmUpController implements TrafficShapingController {...@Overridepublic boolean canPass(Node node, int acquireCount) {return canPass(node, acquireCount, false);}@Overridepublic boolean canPass(Node node, int acquireCount, boolean prioritized) {//获取当前1s的QPSlong passQps = (long) node.passQps();//获取上一窗口通过的QPSlong previousQps = (long) node.previousPassQps();//1.生成令牌并同步到令牌桶内syncToken(previousQps);//获取令牌桶内剩余的令牌数long restToken = storedTokens.get();//2.如果令牌桶中的令牌数量大于告警值,说明还处于预热阶段,此时需要判断令牌的生成速度和消费速度if (restToken >= warningToken) {//获取桶内剩余令牌数超过告警值的令牌个数long aboveToken = restToken - warningToken;//当前令牌的生成间隔 = 稳定阶段的生成间隔 + 桶内超出告警值部分的已存储令牌数 * slope//其中,稳定阶段的生成间隔是1/count,桶内超出告警值部分的已存储令牌数是aboveToken//注意:预热阶段生成令牌的速率会越来越慢,也就是生成令牌的间隔越来越大;//当桶内已存储的令牌超过告警值后,令牌越多,那1秒可允许的QPS越小;//下面代码计算的是://当前1s内的时间窗口能够生成的令牌数量,即当前时间窗口生成的令牌可满足的QPS = 1 / 当前令牌的生成间隔double warningQps = Math.nextUp(1.0 / (aboveToken * slope + 1.0 / count));//如果当前消费令牌的速度(passQps + acquireCount) <= 当前生成令牌的速度(warningQps),则允许通过//如果当前时间窗口通过的QPS + 客户端申请的令牌数 小于等于 当前预热阶段的告警QPS,则代表允许通过if (passQps + acquireCount <= warningQps) {return true;}}//3.如果令牌桶中的令牌数量小于告警值,说明预热结束,进入稳定阶段else {//如果当前消费令牌的速度(passQps + acquireCount) <= 当前生成令牌的速度(count),则允许通过if (passQps + acquireCount <= count) {return true;}}return false;}//生成令牌并同步到令牌桶内//入参passQps是前一个时间窗口的QPS,也就是上一秒通过的QPS数//syncToken()方法的逻辑是://1.首先验证当前时间与最后更新令牌桶的时间,避免在同一个时间窗口重复添加令牌;//2.其次通过WarmUpController.coolDownTokens()方法获取最新的令牌数;//3.接着利用CAS来保证更新令牌桶的线程安全性;//4.最后将桶内已存储的令牌数,减去上一秒通过的QPS数,得到目前令牌桶剩余的令牌数;protected void syncToken(long passQps) {//获取当前时间mslong currentTime = TimeUtil.currentTimeMillis();//将当前时间ms转换为scurrentTime = currentTime - currentTime % 1000;//获取上一次更新令牌桶已存储的令牌数量的时间long oldLastFillTime = lastFilledTime.get();//如果上一次更新令牌桶已存储的令牌数量的时间和当前时间一样,或发生了时钟回拨等情况导致比当前时间还小//那么就无需更新,直接return即可if (currentTime <= oldLastFillTime) {return;}//先获取目前令牌桶已存储的令牌数long oldValue = storedTokens.get();//调用WarmUpController.coolDownTokens()方法得到最新的令牌数long newValue = coolDownTokens(currentTime, passQps);//通过CAS更新令牌桶已存储的令牌数//注意:系统初始化完毕,第一个请求进来调用WarmUpController.canPass()方法时,storedTokens = maxTokenif (storedTokens.compareAndSet(oldValue, newValue)) {//设置令牌桶内已存储的最新令牌数 = 当前令牌数 - 上一个时间窗口通过的请求数long currentValue = storedTokens.addAndGet(0 - passQps);if (currentValue < 0) {storedTokens.set(0L);}//更新最后一次添加令牌的时间戳lastFilledTime.set(currentTime);}}//根据当前时间和上一个时间窗口通过的QPS计算更新后的令牌数private long coolDownTokens(long currentTime, long passQps) {//获取当前令牌桶已存储的令牌数long oldValue = storedTokens.get();long newValue = oldValue;//如果令牌桶中已存储的令牌数小于告警值,说明系统已结束冷启动,即退出预热阶段进入稳定阶段//也就是桶内已存储的令牌数没有达到进入预热阶段的阈值,此时需要较快地向令牌桶中添加令牌if (oldValue < warningToken) {//在当前令牌数量的基础上,加上从上次添加令牌到现在经过的时间(以秒为单位)乘以令牌生成速率(QPS阈值count)newValue = (long)(oldValue + (currentTime - lastFilledTime.get()) * count / 1000);}//如果令牌桶中已存储的令牌数大于告警值,说明系统处于预热阶段,还在进行冷启动else if (oldValue > warningToken) {//如果上一个时间窗口通过的QPS,小于系统最冷时允许通过的QPS(1 / (1 / count * coldFactor))//那么就说明当前系统的负载比较低,可以向令牌桶中添加令牌if (passQps < (int)count / coldFactor) {//在当前令牌数量的基础上,加上从上次添加令牌到现在经过的时间(以秒为单位)乘以令牌生成速率(QPS阈值count)newValue = (long)(oldValue + (currentTime - lastFilledTime.get()) * count / 1000);}}//确保令牌桶更新后的令牌数不超过最大令牌数(maxToken)//系统初始化完毕,第一个请求进来调用WarmUpController.canPass()方法时,//oldValue = 0,lastFilledTime = 0,此时返回maxTokenreturn Math.min(newValue, maxToken);}
}
(5)Sentinel中的令牌桶算法总结
WarmUpController的核心原理是:首先根据当前时间和上一个时间窗口通过的QPS同步令牌桶内的令牌数。然后比较桶内令牌数和告警值,计算当前时间窗口允许通过的告警QPS。最后比较当前请求下的QPS是否大于允许通过的告警QPS来决定限流。
注意:系统在预热阶段会逐渐提高令牌的生成速度,从而平滑过渡到稳定阶段。当系统启动时,桶内令牌数最大,令牌生成速率最低,允许的QPS最低。随着桶内令牌数减少,令牌生成速度逐渐提高,允许的QPS也逐渐提高。最后到达稳定阶段,此时允许的QPS便是FlowRule中设置的QPS阈值。
所以根据稳定阶段令牌的生成速率是1/count,默认冷却因子为3,得出系统最冷时令牌的生成速率是3/count。因此预热阶段一开始允许的QPS为count/3,预热完毕的QPS就是count。
相关文章:
Sentinel源码—9.限流算法的实现对比二
大纲 1.漏桶算法的实现对比 (1)普通思路的漏桶算法实现 (2)节省线程的漏桶算法实现 (3)Sentinel中的漏桶算法实现 (4)Sentinel中的漏桶算法与普通漏桶算法的区别 (5)Sentinel中的漏桶算法存在的问题 2.令牌桶算法的实现对比 (1)普通思路的令牌桶算法实现 (2)节省线程的…...
单片机外设模块汇总与介绍
一、基础外设 GPIO(通用输入输出) 功能:数字信号输入/输出,支持推挽、开漏模式。 应用:控制LED、按键检测、数字传感器接口。 配置要点: 输入模式:上拉/下拉电阻配置 输出模式:…...
git lfs下载大文件限额
起因是用 model.load_state_dict(torch.load())加载pt权重文件时,出现错误:_pickle.UnpicklingError: invalid load key, ‘v’. GPT告诉我:你的 pt 文件不是权重文件,而是模型整体保存(或根本不是 PyTorch 文件&#…...
第4天:Linux开发环境搭建
🧰 第4天:Linux开发环境搭建 一、GCC 编译器 📌 1. 什么是 GCC? GCC(GNU Compiler Collection)是 GNU 工程开发的编译器集合,主要支持 C、C、Fortran 等语言的编译,是 Linux 系统中…...
“在中国,为中国” 英飞凌汽车业务正式发布中国本土化战略
3月28日,以“夯实电动化,推进智能化,实现高质量发展”为主题的2025中国电动汽车百人会论坛在北京举办。众多中外机构与行业上下游嘉宾就全球及中国汽车电动化的发展现状、面临的挑战与机遇,以及在技术创新、市场布局、供应链协同等…...
【AI应用】免费代码仓构建定制版本的ComfyUI应用镜像
免费代码仓构建定制版本的ComfyUI应用镜像 1 创建代码仓1.1 注册登陆1.2 创建代码仓1.5 安装中文语言包1.4 拉取ComfyUI官方代码2 配置参数和预装插件2.1 保留插件和模型的版本控制2.2 克隆插件到代码仓2.2.1 下载插件2.2.2 把插件设置本仓库的子模块管理3 定制Docker镜像3.1 创…...
新市场环境下新能源汽车电流传感技术发展前瞻
新能源革命重构产业格局 在全球碳中和战略驱动下,新能源汽车产业正经历结构性变革。国际清洁交通委员会(ICCT)最新报告显示,2023年全球新能源汽车渗透率突破18%,中国市场以42%的市占率持续领跑。这种产业变革正沿着&q…...
ctfhub-RCE
关于管道操作符 windows: 1. “|”:直接执行后面的语句。 2. “||”:如果前面的语句执行失败,则执行后面的语句,前面的语句只能为假才行。 3. “&”:两条命令都执行,如果前面的语句为假则直…...
SSE(Server-Sent Events)技术详解:轻量级实时通信的全能方案
一、实时通信技术演进与SSE定位 1.1 主流实时通信技术对比 #mermaid-svg-1VQcZqAOmMoxosiW {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-1VQcZqAOmMoxosiW .error-icon{fill:#552222;}#mermaid-svg-1VQcZqAOmMox…...
QT项目----电子相册(4)
文章目录 前言一、右侧区域PicShow1.创建PicShow2.创建PicButton3.效果图qss 4.设置动画QGraphicsOpacityEffectQPropertyAnimation 5.双击左侧图片目录 右侧显示图片解决缩放时卡顿的问题 二、删除相册实现思路代码实现 总结 前言 提示:这里可以添加本文要记录的大…...
Sentinel源码—9.限流算法的实现对比一
大纲 1.漏桶算法的实现对比 (1)普通思路的漏桶算法实现 (2)节省线程的漏桶算法实现 (3)Sentinel中的漏桶算法实现 (4)Sentinel中的漏桶算法与普通漏桶算法的区别 (5)Sentinel中的漏桶算法存在的问题 2.令牌桶算法的实现对比 (1)普通思路的令牌桶算法实现 (2)节省线程的…...
46. 全排列
题目 给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 示例 1: 输入:nums [1,2,3] 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]示例 2: 输入:…...
UML 顺序图:电子图书馆管理系统的交互之道
目录 一、初识 UML 顺序图 二、电子图书馆管理系统顺序图解析 (一)借阅流程 (二)归还流程 三、顺序图绘画 四、顺序图的优势与价值 五、总结 UML 顺序图是描绘系统组件交互的有力工具。顺序图直观展示消息传递顺序与对象协…...
前端渲染pdf文件解决方案-pdf.js
目录 一、前言 二、简介 1、pdf.js介绍 2、插件版本参数 三、通过viewer.html实现预览(推荐) 1、介绍 2、部署 【1】下载插件包 【2】客户端方式 【3】服务端方式(待验证) 3、使用方法 【1】预览PDF文件 【2】外部搜索…...
接口测试和功能测试详解
🍅 点击文末小卡片 ,免费获取软件测试全套资料,资料在手,涨薪更快 本文主要分为两个部分: 第一部分:主要从问题出发,引入接口测试的相关内容并与前端测试进行简单对比,总结两者…...
LeetCode热题100--283.移动零--简单
1.题目 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 请注意 ,必须在不复制数组的情况下原地对数组进行操作。 示例 1: 输入: nums [0,1,0,3,12] 输出: [1,3,12,0,0] 示例 2: 输入: nums [0]…...
CoT 数据集如何让大模型学会「一步一步思考」?
目前,大模型的回答路径基本遵循input-output的方式,在面对复杂任务时表现不佳。反之,人类会遵循一套有条理的思维流程,逐步推理得出正确答案。这种差异促使人们深入思考:如何才能让大模型“智能涌现”,学会…...
量子跃迁:Vue组件安全工程的基因重组与生态免疫(完全体)
总章数字免疫系统的解剖学革命 在2024年某国家级数字政务平台的安全审计中,传统前端架构暴露出的信任链断裂问题,导致公民隐私数据以每秒23TB的速度在暗网流通。当我们用PET扫描技术观察现代Web应用的微观结构,发现94.7%的安全威胁源自组件间…...
配置 Nginx 的 HTTPS
证书文件 文件名 作用 来源 example.com.key 服务器的私钥,用于加密和解密数据。 本地生成 -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAqp5c... -----END RSA PRIVATE KEY----- example.com.csr 证书签名请求文件,包含公钥和申请者信息&…...
FPGA开发流程初识
FPGA 的开发流程可知,在 FPGA 开发的过程中会产生很多不同功能的文件,为了方便随时查找到对应文件,所以在开始开发设计之前,我们第一个需要考虑的问题是工程内部各种文件的管理。如 果不进行文件分类,而是将所有文件…...
c++中的enum变量 和 constexpr说明符
author: hjjdebug date: 2025年 04月 23日 星期三 13:40:21 CST description: c中的enum变量 和 constexpr说明符 文章目录 1.Q:enum 类型变量可以有,--操作吗?1.1补充: c/c中enum的另一个细微差别. 2.Q: constexpr 修饰的函数,要求传入的参数必需是常量吗ÿ…...
JVM学习笔记
1、jvm概述 1.1、即时编译 为什么java没有c和c快 多了一层解释,而即时编译就是将热点数放入内存,下次执行的时候不用解释,提高了效率 1.2、常见的jvm jvm不止一个,意不意外?惊不惊喜? 1.3、hotspot的发展…...
AtCoder Beginner Contest 402题解
A - CBC 思路:仔细看这题其实就发现,我们只需要遍历一遍字符串把大写字母输出即可,很标准的签到题 #include<bits/stdc.h> using namespace std; #define int long longsigned main() {string s;cin>>s;for(char c:s){if(c>A…...
Rust 语言使用场景分析
1. 引言:Rust 语言概述 Rust 是一门专注于性能、内存安全和并发性的现代系统编程语言。自 2010 年由 Mozilla Research 的 Graydon Hoare 发起,并于 2015 年正式发布以来,Rust 凭借其独特的设计理念和强大的功能集,在技术领域迅速…...
HTTP 请求头的 key 不区分大小写。
详细说明 HTTP 协议规范 根据 RFC 7230,HTTP 头字段的名称(即 key)在传输时不区分大小写。例如,Content-Type 和 content-type 被视为相同的字段。 实际行为 客户端行为:大多数 HTTP 客户端(如浏览器、cur…...
4.RabbitMQ - 延迟消息
RabbitMQ延迟消息 文章目录 RabbitMQ延迟消息一、延迟消息介绍二、实现2.1 死信交换机2.2 延迟消息插件2.3 取消超时订单 一、延迟消息介绍 延迟消息:生产者发送消息时指定一个时间,消费者不会立刻收到消息,而是在指定时间后才收到消息 用户…...
C++学习:六个月从基础到就业——STL算法(一) 基础与查找算法
C学习:六个月从基础到就业——STL算法(一) 基础与查找算法 本文是我C学习之旅系列的第二十五篇技术文章,也是第二阶段"C进阶特性"的第三篇,主要介绍C STL算法库的基础知识与查找类算法。查看完整系列目录了解…...
springboot+vue 支付宝支付(沙箱方式,测试环境使用)
准备工作: 1 支付宝沙箱环境的公钥,私钥配置,查询等使用,如果是用自定义的方式,需要下生成公钥,私钥的工具,否则不需要 登录 - 支付宝 小程序文档 - 支付宝文档中心 2 本地测试时&…...
安装win11自带linux是报错:WslRegisterDistribution failed with error: 0x800701bcErr
确保系统设置中的“打开win11的子系统”已打钩 管理员身份运行cmd,并输入如下 然后再重启ubantu...
面试经历(一)雪花算法
uid生成方面 1:为什么用雪花算法 分布式ID的唯一性需要保证,同时需要做到 1:单调递增 2:确保安全,一个是要能体现出递增的单号,二一个不能轻易的被恶意爬出订单数量 3:含有时间戳 4&#…...
docker底层原理简述
前言 平时用docker很多,今天深入了解下docker原理层面的实现,包括docker核心概念,文件系统,资源隔离,网络通信等 参考文章: Docker底层原理(图解秒懂史上最全) - 疯狂创客圈 - 博…...
【6D位姿估计】Foundation Pose复现
主要参考 项目仓库README站内其他博文 注意事项 容器化部署不难,主要是部署docker本身会存在一些环境问题,重点关注访问外网的端口需要手动调整至与魔法相同,可以参考: docker部署在启动容器镜像后,需要注意镜像当前…...
TDengine 数据订阅设计
简介 数据订阅作为 TDengine 的一个核心功能,为用户提供了灵活获取所需数据的能力。通过深入了解其内部原理,用户可以更加有效地利用这一功能,满足各种实时数据处理和监控需求。 基本概念 主题 与 Kafka 一样,使用 TDengine 数…...
VMware Fusion Pro 13 Mac版虚拟机 安装Win11系统教程
Mac分享吧 文章目录 Win11安装完成,软件打开效果一、VMware安装Windows11虚拟机1️⃣:准备镜像2️⃣:创建虚拟机3️⃣:虚拟机设置4️⃣:安装虚拟机5️⃣:解决连不上网问题 安装完成!࿰…...
高并发下单库存扣减异常?飞算 JavaAI 自动化生成分布式事务解决方案
在电商、旅游等行业业务量激增,高并发下单场景中,传统库存扣减方式弊端尽显。超卖问题因缺乏有效并发控制机制频发,多个订单同时访问库存数据,导致同一商品多次售出,订单无法履约引发客户投诉;同时…...
crictl 遇到报错 /run/containerd/containerd.sock: connect: permission denied
报错内容 crictl --runtime-endpoint unix:///run/containerd/containerd.sock logs CONTAINERID FATA[0000] validate service connection: validate CRI v1 runtime API for endpoint "unix:///run/containerd/containerd.sock": rpc error: code Unavailable de…...
CSS外边距合并现象
外边距合并(Margin Collapsing)是指在文档流中,两个或多个相邻元素的外边距(margin)会合并为一个外边距,其大小会取其中最大的外边距值 当两个相邻的兄弟元素之间没有其他内容(如边框、内边距、…...
《深度神经网络之数据增强、模型保存、模型调用、学习率调整》
文章目录 前言一、数据增强1、什么是数据增强?2、数据增强的实现方法(1)几何变换翻转:旋转:平移: (2)颜色变换亮度调整:对比度调整:色彩抖动: (3&…...
【Java学习笔记】random的使用
random使用方法 使用说明:返回的是(0<n<1)这个范围中的任意带正号的double值 代码实例 public class helloworld{public static void main(String[] args){System.out.println(Math.random());} }生成0-100中的任意数代码示例 public class Main {public …...
Redis的string类型使用
第一步:添加缓存 以若依岗位代码为例 一:首先从redis中查询岗位信息,如果查询到了则直接返回。 二:如果redis中没有数据,则直接从数据库中查询。查询后放到redis并返回 package com.ruoyi.system.service.impl;imp…...
AIGC架构与原理
AIGC(AI Generated Content,人工智能生成内容)的架构与原理 AIGC通过整合数据采集、模型训练、推理服务等模块,结合深度学习与生成对抗网络(GAN)等技术,实现从数据到内容的自动化生成。 一、AIG…...
安全复健|windows常见取证工具
写在前面: 此博客仅用于记录个人学习内容,学识浅薄,若有错误观点欢迎评论区指出。欢迎各位前来交流。(部分材料来源网络,若有侵权,立即删除) 取证 01系统运行数据 使用工具:Live-F…...
Oracle EBS R12.2 汉化
一、前言 在使用oracle ebs时,使用中文会更好的理解整个ebs流程,以下介绍oracle r12中文补丁的方式 如果你的系统除了支持英语外,还支持其他语言,比如中文,那你在下载补丁的时候除了下载Generic Platform版本外&#…...
【Java面试笔记:基础】12.Java有几种文件拷贝方式?哪一种最高效?
在 Java 中,文件拷贝可以通过多种方式实现,不同方式的性能和适用场景有所差异。 1. Java 文件拷贝方式 传统 IO 方式 使用 FileInputStream 和 FileOutputStream,通过循环读取和写入数据实现文件拷贝。 示例代码: try (InputStream is = new FileInputStream("sou…...
互联网大厂Java面试:RocketMQ、RabbitMQ与Kafka的深度解析
互联网大厂Java面试:RocketMQ、RabbitMQ与Kafka的深度解析 面试场景 面试官:马架构,您好!欢迎参加我们的面试。今天我们将围绕消息中间件展开讨论,尤其是RocketMQ、RabbitMQ和Kafka。您有十年的Java研发和架构设计经…...
kali安装切换jdk1.8.0_451java8详细教程
kali安装切换jdk1.8.0_451java8详细教程 下载链接: jdk-8u451-linux-i586.tar.gz 链接: https://pan.baidu.com/s/1lpgI0JMfHpZ__RxsF8UoBw?pwdx3z2 提取码: x3z2 解压jdk 首先将下载好的压缩包放在kali虚拟机中,一般是直接拖到桌面 然后cd到压缩包…...
众趣科技X世界读书日丨数字孪生技术赋能图书馆空间智慧化运营
4月23日,是第30个“世界读书日”,不仅是庆祝阅读的日子,更是思考知识传播未来的契机。 图书馆作为主要传播图书的场所,在科技的发展中,图书馆正面临前所未有的挑战,联合国数据显示,全球近30%的…...
Python内置函数-aiter()
Python内置函数 aiter() 用于获取异步可迭代对象的异步迭代器,是异步编程中的核心工具之一。 1. 基本概念 异步可迭代对象:实现了 __aiter__() 和 __anext__() 方法的对象,支持 async for 循环。 异步迭代器:通过 aiter() 获取的…...
Java 实现单链表翻转(附详细注释)
1. 引言 单链表(Singly Linked List)是一种常见的数据结构,在算法和数据结构的学习中占有重要地位。翻转单链表是一道经典的面试题,本文将介绍几种常见的 Java 实现方法,并详细讲解关键步骤的含义。 2. 单链表定义 …...
基于HPC的气候模拟GPU加速实践全流程解析
基于HPC的气候模拟GPU加速实践全流程解析 关键词:气候模型、GPU加速、CUDA编程、性能优化、分布式训练 摘要: 本文针对全球气候模拟中10^12级网格点实时计算需求,提出基于CUDA的并行计算架构。通过改进WRF模式的分块矩阵乘法算法,…...