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

Sentinel源码—9.限流算法的实现对比一

大纲

1.漏桶算法的实现对比

(1)普通思路的漏桶算法实现

(2)节省线程的漏桶算法实现

(3)Sentinel中的漏桶算法实现

(4)Sentinel中的漏桶算法与普通漏桶算法的区别

(5)Sentinel中的漏桶算法存在的问题

2.令牌桶算法的实现对比

(1)普通思路的令牌桶算法实现

(2)节省线程的令牌桶算法实现

(3)Guava中的令牌桶算法实现

(4)Sentinel中的令牌桶算法实现

(5)Sentinel中的令牌桶算法总结

1.漏桶算法的实现对比

(1)普通思路的漏桶算法实现

(2)节省线程的漏桶算法实现

(3)Sentinel中的漏桶算法实现

(4)Sentinel中的漏桶算法与普通漏桶算法的区别

(5)Sentinel中的漏桶算法存在的问题

(1)普通思路的漏桶算法实现

一.漏桶算法的处理流程

二.漏桶算法的主要特点

三.漏桶算法的优缺点

一.漏桶算法的处理流程

漏桶算法的核心思想是以固定速率流出。

步骤一:当新的请求到达时,会将新的请求放入缓冲区(请求队列)中,类似于往水桶里注水。

步骤二:系统会以固定的速度处理缓冲区中的请求,类似于水从窟窿中以固定的速度流出,比如开启一个后台线程定时以固定的速度从缓冲区中取出请求然后进行分发处理。

步骤三:如果缓冲区已满,则新的请求将被拒绝或丢弃,类似于水溢出。

二.漏桶算法的主要特点

特点一:固定速率

水从桶底的孔洞中以固定速率流出,类似于网络中以固定速率发送数据包。但写入速度不固定,也就是请求不是匀速产生的。相当于生产者生产消息不固定,消费者消费消息是匀速消费的。

特点二:有限容量

桶的容量有限,当桶满时,新到达的水会溢出,即拒绝超过容量的请求。

特点三:先进先出(FIFO)

水按照先进先出的顺序从桶中流出,类似于请求的处理顺序。

这种算法的一个重要特性是:无论请求的接收速率如何变化,请求的处理速率始终是稳定的,这就确保了系统的负载不会超过预设的阈值。但是由于请求的处理速率是固定的,所以无法处理突发流量。此外如果入口流量过大,漏桶可能会溢出,导致请求丢失。

三.漏桶算法的优缺点

优点一:平滑流量

由于以固定的速率处理请求,所以可以有效地平滑和整形流量,避免流量的突发和波动,类似于消息队列的削峰填谷的作用。

优点二:防止过载

当流入的请求超过桶的容量时,可以直接丢弃请求,防止系统过载。

缺点一:无法处理突发流量

由于漏桶的出口速度是固定的,无法处理突发流量。例如,即使在流量较小的时候,也无法以更快的速度处理请求。

缺点二:可能会丢失数据

如果入口流量过大,超过了桶的容量,那么就需要丢弃部分请求。在一些不能接受丢失请求的场景中,这可能是一个问题。

缺点三:不适合处理速率变化大的场景

如果处理速率变化大,或需要动态调整处理速率,则无法满足。

漏桶算法适用于需要以固定速率处理请求的场景。在多数业务场景中,其实并不需要按照严格的速率进行请求处理。而且多数业务场景都需要应对突发流量的能力,所以会使用令牌桶算法。

(2)节省线程的漏桶算法实现

漏桶算法可以通过延迟计算的方式来实现。延迟计算指的是不需要单独的线程来定时生成令牌或从漏桶中定时取请求,而是由调用限流器的线程自己计算是否有足够的令牌以及需要sleep的时间。延迟计算的方式可以节省一个线程资源。

public class LeakyBucketLimiter {//桶的最大容量public static long threshold = 10;//当前桶内的水量public static long count = 0;//漏水速率(每秒5次)public static long leakRate = 5;//上次漏水时间public static long lastLeakTime = System.currentTimeMillis();//限流方法,返回true表示通过public boolean canPass() {//调用漏水方法this.leak();//判断是否超过最大请求数量if (count < threshold) {count++;return true;}return false;}//漏水方法,计算并更新这段时间内漏水量private void leak() {//获取系统当前时间long currentTime = System.currentTimeMillis();//计算这段时间内,需要流出的水量long leakWater = (currentTime - lastLeakTime) * leakRate / 1000;count = Math.max(count - leakWater, 0);//更新最近一次的漏水时间lastLeakTime = currentTime;}
}

(3)Sentinel中的漏桶算法实现

在RateLimiterController的canPass()方法中,为了判断是否超出QPS阈值,通过原子类变量latestPassedTime简化成单线程让请求先后通过的处理模型。为了尽量让业务不受Sentinel影响,采用预估请求的被处理时间点的方式。也就是无需等前面的请求完全被处理完,才确定后面的请求被处理的时间。因为在普通的漏桶算法中,是处理完一个请求,才从漏桶取出水滴。而RateLimiterController的漏桶算法,则是假设请求已经被通过了。

具体的判断逻辑如下:首先获取系统的当前时间currentTime。然后计算在满足流控规则中限制的QPS阈值count的情况下,先后的两个请求被允许通过时的最小时间间隔costTime。接着计算当前请求最早的预期通过时间expectedTime,也就是此次请求预计会在几时几分几秒内通过。最后比较expectedTime和currentTime就可知当前请求是否允许通过了。

一.如果expectedTime小于等于currentTime

也就是当前请求最早的预期通过时间比系统当前时间小。如果在此时(currentTime)通过当前请求,则当前请求的通过时间就比它最早的预期通过时间(expectedTime)要晚,即当前请求和最近通过的请求的时间间隔变大了,所以此时不会超QPS阈值。于是返回true允许通过,同时更新最近允许请求通过的时间戳为当前时间。

二.如果expectedTime大于currentTime

也就是当前请求最早的预期通过时间比系统当前时间大。如果在此时(currentTime)通过当前请求,则当前请求的通过时间就比它最早的预期通过时间(expectedTime)要早,即当前请求和最近通过的请求的时间间隔变小了,比最小间隔时间costTime还小,所以此时必然会超QPS阈值。因此返回进行等待或者返回false不允许通过,等待的最小时间就是:最近通过请求的时间 + 先后两个请求允许通过时的最小间隔时间 - 当前时间。

需要注意:Sentinel流量控制的漏桶算法,只能限制在costTime内的流量激增,限制不了costTime外的流量激增。比如系统启动完一瞬间就涌入大量并发请求,此时的流量激增限制不了。又比如系统处理完正常流量的最后一个请求,隔了costTime+的时间后,突然涌入超QPS阈值的并发请求,此时也限制不了这种情况的流量激增。但如果系统处理完正常流量的最后一个请求,隔了costTime-的时间后,突然涌入超QPS阈值的并发请求,此时则可以限制这种情况的流量激增。

同时,为了避免等待的各个并发线程被同时唤醒,可以利用原子变量的addAndGet()方法 + 假设等待请求已被通过的方式,实现需要等待的并发请求进行睡眠等待的时间都不一样,从而实现并发请求排队等待的效果。

实现排队等待效果的核心逻辑:由于latestPassedTime的原子性,每个线程都会获得不一样的oldTime。接着根据oldTime - 当前时间,就可以得到每个线程需要睡眠等待的时间waitTime。此时的waitTime都将会不一样,从而避免并发线程同时被唤醒的情况。将latestPassedTime按costTime进行自增,其实相当于假设当前请求在不超过QPS阈值的情况下,被允许通过了。

public class RateLimiterController implements TrafficShapingController {//排队等待的意思是超出阈值后等待一段时间,maxQueueingTimeMs就是请求在队列中的最大等待时间private final int maxQueueingTimeMs;//流控规则中限制QPS的阈值,也就是QPS超出多少后会进行限制private final double count;//最近允许一个请求通过的时间,每次请求通过后就会更新此时间,可以根据该时间计算出当前请求最早的预期通过时间//注意:Sentinel是在业务前面的,尽量不要让业务受到Sentinel的影响,所以不需要等请求完全被处理完,才确定请求被通过的时间private final AtomicLong latestPassedTime = new AtomicLong(-1);public RateLimiterController(int timeOut, double count) {this.maxQueueingTimeMs = timeOut;this.count = count;}@Overridepublic boolean canPass(Node node, int acquireCount) {return canPass(node, acquireCount, false);}@Overridepublic boolean canPass(Node node, int acquireCount, boolean prioritized) {//acquireCount代表每次从桶底流出多少个请求//如果acquireCount小于等于0,则表示无需限流直接通过,不过acquireCount一般默认是1if (acquireCount <= 0) {return true;}//如果限流规则的count(即限制QPS的阈值)小于等于0,则直接拒绝,相当于一个请求也不能放行if (count <= 0) {return false;}//1.首先获取系统的当前时间long currentTime = TimeUtil.currentTimeMillis();//2.然后计算,在满足流控规则中限制的QPS阈值count的情况下,先后的两个请求被允许通过时的最小间隔时间(假设请求是单线程处理的)long costTime = Math.round(1.0 * (acquireCount) / count * 1000);//3.接着计算当前请求最早的预期通过时间 = 满足QPS阈值下的两个请求的最小时间间隔 + 上次请求的通过时间long expectedTime = costTime + latestPassedTime.get();//4.最后判断当前请求最早的预期通过时间是否比系统当前时间小if (expectedTime <= currentTime) {//等价于没有超出QPS阈值//当前请求最早的预期通过时间比系统当前时间小//如果在此时(currentTime)通过当前请求,那么当前请求的实际通过时间就比它最早的预期通过时间(expectedTime)要晚//也就是当前请求和最近通过的请求的时间间隔变大了,所以此时不会超QPS阈值,返回true允许通过//由这里可知,latestPassedTime并不会影响costTime,也就是说,多个线程可以并发执行到这里而不受阈值的影响//这意味着,Sentinel流量控制的漏桶算法,只能限制在costTime时间内的流量激增,限制不了costTime时间外的流量激增//比如系统启动完的那一瞬间就涌入超出QPS阈值的并发请求,此时的这种流量激增是限制不了的;//又比如系统正常运行时处理完了正常流量的最后一个请求,隔了costTime+的时间后,突然涌入超出QPS阈值的并发请求,此时也限制不了;//只能限制住这样的一种情况:系统正常运行处理完正常流量的最后一个请求,隔了costTime-的时间,突然涌入超出QPS阈值的并发请求latestPassedTime.set(currentTime);return true;} else {//如果不是,即当前请求最早的预期通过时间比系统当前时间大//则说明latestPassedTime.get()大了,也就是上一个可能由于QPS超出阈值的原因导致请求处理慢了,所以需要进行等待//计算当前请求的等待时间,用于判断是否超出流控规则设置的最大等待时间long waitTime = costTime + latestPassedTime.get() - TimeUtil.currentTimeMillis();if (waitTime > maxQueueingTimeMs) {//如果超出最大等待时间,则直接返回falsereturn false;} else {//等价于超出了QPS阈值//当前请求最早的预期通过时间比系统当前时间大//如果在此时(currentTime)通过当前请求,那么当前请求的实际通过时间就比它最早的预期通过时间(expectedTime)要早//也就是当前请求和最近通过的请求的时间间隔变小了,比最小间隔时间costTime还小//所以此时必然会超QPS阈值,因此返回进行等待或者返回false不允许通过//而等待的最小时间,就是最近通过请求的时间 + 先后两个请求允许通过时的最小间隔时间 - 当前时间//首先通过latestPassedTime这个原子变量的addAndGet()方法//将最近通过请求的时间latestPassedTime,加上先后两次请求需要的最小间隔时间costTime,得到当前请求本来预期的通过时间//注意://当多个并发线程执行到此处时,由于latestPassedTime的原子性,每个线程都会获得不一样的oldTime//接着根据oldTime - 当前时间,就可以得到每个线程需要睡眠等待的时间waitTime//此时的waitTime都将会不一样,从而避免并发线程同时被唤醒的情况//将latestPassedTime进行自增,其实相当于假设当前请求在不超过QPS阈值的情况下,被允许通过了long oldTime = latestPassedTime.addAndGet(costTime);try {//然后计算当前请求需要等待多久 = 当前请求最早的预期通过时间 - 当前系统时间waitTime = oldTime - TimeUtil.currentTimeMillis();//如果等待时间大于流控规则设置的最大等待时间,则需要回滚刚才更新的最近通过请求的时间//也就是将latestPassedTime减去costTime,然后返回false表示请求无法通过if (waitTime > maxQueueingTimeMs) {//如果发现新计算的等待时间 大于 最大等待时间,则需要回滚latestPassedTimelatestPassedTime.addAndGet(-costTime);return false;}//in race condition waitTime may <= 0if (waitTime > 0) {//当前请求需要进行等待Thread.sleep(waitTime);}return true;} catch (InterruptedException e) {}}}return false;}
}

(4)Sentinel中的漏桶算法与普通漏桶算法的区别

区别一:普通漏桶算法使用的是真实队列

节省线程的漏桶算法会有一个单独的字段去记录当前桶内的水量,也就是请求量。每通过一个请求,则该字段值-1。反之,每新进一个请求,此字段值+1。

区别二:Sentinel漏桶算法使用的是虚拟队列

它没有单独的字段去记录当前桶内的请求量,而是根据最近通过请求的时间得出当前请求最早的预期通过时间来实现。其本质就是先假设当前请求可以通过,然后再按照先后请求在QPS阈值下可以允许通过时的最大时间间隔,来计算出当前请求最早的预期通过时间,再对比是否和当前发生冲突。

区别三:普通漏桶算法使用的策略是直接拒绝

如果流入速度大于流出速度,则直接拒绝。

区别四:Sentinel漏桶算法使用的策略是排队等待

如果超出了阈值,则不会直接拒绝请求,而是会等待一段时间,只要在这段时间内能处理到这个请求就不会拒绝掉。

(5)Sentinel中的漏桶算法存在的问题

问题一:在costTime时间内出现流量激增才能限流

如果在costTime时间外,即最后一次请求通过的时间已经好久了,突然流量激增以及并发进入系统,那么此时是无法限制住的。

问题二:Sentinel排队等待流控效果支持的QPS阈值不能超过1000

如果超过1000,且costTime计大于等于0.5,则会认为间隔时间都是1ms。如果costTime小于0.5,则认为配置失效,相当于没有配置此条流控规则。

long costTime = Math.round(1.0 * (acquireCount) / count * 1000);
long costTime = Math.round(1.0 * (1) / 1100 * 1000)约等于0.9ms;

默认情况下,acquireCount的值是1,那么:如果QPS阈值count在1000~2000,此时costTime = 1,限流不受阈值影响。如果QPS阈值count大于2000,此时costTime = 0,限流配置失效。

2.令牌桶算法的实现对比

(1)普通思路的令牌桶算法实现

(2)节省线程的令牌桶算法实现

(3)Guava中的令牌桶算法实现

(4)Sentinel中的令牌桶算法实现

(5)Sentinel中的令牌桶算法总结

(1)普通思路的令牌桶算法实现

一.令牌桶算法的处理流程

二.令牌桶算法的特点

三.令牌桶算法的优缺点

四.漏桶算法和令牌桶算法的对比

五.令牌桶算法和漏桶算法的核心区别

一.令牌桶算法的处理流程

令牌桶算法的核心思想是以固定速率流入。

步骤一:初始化令牌桶,设置其容量和生成速率。

步骤二:当有新请求到来时,检查令牌桶中是否有足够的令牌。如果有足够的令牌,则允许请求通过,并从令牌桶中扣除相应数量令牌。如果没有足够的令牌,则拒绝请求。

步骤三:系统会以固定的速度添加令牌,直到达到令牌桶容量,比如开启一个后台线程以固定的速度向令牌桶中添加令牌。

二.令牌桶算法的特点

特点一:支持突发流量

令牌桶算法允许在限流内应对突发流量,有助于提高系统的响应能力。

特点二:平滑处理速率

和漏桶算法一样,令牌桶算法也可以平滑处理流量,避免处理速率突变。

令牌桶算法的一个重要特性是,它能够处理突发流量。当桶中有足够的令牌时,可以一次性处理多个请求。这对于需要处理突发流量的应用场景非常有用,但是又不会无限制的增加处理速率导致压垮服务器,因为桶内令牌数量是有限制的。

三.令牌桶算法的优缺点

优点一:可以处理突发流量

令牌桶算法可以处理突发流量。当桶满时,能够以最大速度处理请求。这对于需要处理突发流量的应用场景非常有用。

优点二:限制请求处理的平均速率

在长期运行中,请求的处理速率会被限制在预定义的平均速率下,也就是生成令牌的速率。

优点三:灵活性

与漏桶算法相比,令牌桶算法提供了更大的灵活性。例如,可以动态地调整生成令牌的速率。

缺点一:可能导致过载

如果令牌产生速度过快,可能会导致大量突发流量,使网络或服务过载。

缺点二:需要存储空间

令牌桶需要一定的存储空间来保存令牌,可能会导致内存资源的浪费。

四.漏桶算法和令牌桶算法的对比

漏桶算法是突然往桶里注水,但是漏水的窟窿是固定大小的,因此流出水的速度是固定的,也就是"生产不限速,消费限速"。

令牌桶算法是突然从桶中抽水,也就是固定大小的窟窿变成了入水口,而没桶盖的桶口变成了出水口。相当于入水速度变得固定了,而出水速度不做限制了,也就是"生产限速,消费不限速"。

五.令牌桶算法和漏桶算法的核心区别

令牌桶算法允许用户在短时间内发起更多请求,从而支持突发流量。漏桶算法只能支持每秒固定处理一定数量的请求,从而不支持突发流量。这就是令牌桶和漏桶的核心区别。

(2)节省线程的令牌桶算法实现

令牌桶算法可以通过延迟计算的方式来实现。延迟计算指的是不需要单独的线程来定时生成令牌或从漏桶中定时取请求,而是由调用限流器的线程自己计算是否有足够的令牌以及需要sleep的时间。延迟计算的方式可以节省一个线程资源。

public class TokenBucketLimiter {//桶的最大容量public static long threshold = 10;//当前桶内的令牌数public static long count = 0;//令牌生成速率(每秒5次)public static long tokenRate = 5;//上次生成令牌的时间public static long lastRefillTime = System.currentTimeMillis();//限流方法,返回true表示通过public boolean canPass() {//调用生成令牌方法this.refillTokens();//判断桶内是否还有令牌if (count > 0) {count--;return true;}return false;}//生成令牌方法,计算并更新这段时间内生成的令牌数量private void refillTokens() {long currentTime = System.currentTimeMillis();//计算这段时间内,需要生成的令牌数量long refillTokens = (currentTime - lastRefillTime) * tokenRate / 1000;//更新桶内的令牌数count = Math.min(count + refillTokens, threshold);//更新令牌生成时间lastRefillTime = currentTime;}
}

(3)Guava中的令牌桶算法实现

一.SmoothBursty的初始化

二.SmoothBursty的acquire()方法

三.SmoothWarmingUp的初始化

四.SmoothWarmingUp的acquire()方法

SmoothBursty和SmoothWarmingUp这两种限流器都使用了预支令牌的思路来实现,就是当前线程获取令牌的代价(阻塞时间)需要由下一个线程来支付。这样可以减少线程阻塞的概率,因为下一个请求不确定什么时候才来。如果下一个请求很久才来,那么这段时间产生的新令牌已满足下一个线程的需求,这样就不用阻塞了。

一.SmoothBursty的初始化

RateLimiter不保存上一次请求的时间,但是它保存下一次请求期望到达的时间。如果下一个请求的预期到达时间实际上已经过去了,并且假设下次请求期望到达的时间点是past,现在的时间点是now。那么now - past的这段时间表示RateLimiter没有被使用,所以在这段空闲的时间内RateLimiter就会增加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即20万us的间隔进行令牌发放. 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;}}...
}

二.SmoothBursty的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 SmoothBursty extends SmoothRateLimiter {...@Overridelong storedPermitsToWaitTime(double storedPermits, double permitsToTake) {return 0L;}@Overridedouble coolDownIntervalMicros() {return stableIntervalMicros;}}...
}

相关文章:

Sentinel源码—9.限流算法的实现对比一

大纲 1.漏桶算法的实现对比 (1)普通思路的漏桶算法实现 (2)节省线程的漏桶算法实现 (3)Sentinel中的漏桶算法实现 (4)Sentinel中的漏桶算法与普通漏桶算法的区别 (5)Sentinel中的漏桶算法存在的问题 2.令牌桶算法的实现对比 (1)普通思路的令牌桶算法实现 (2)节省线程的…...

46. 全排列

题目 给定一个不含重复数字的数组 nums &#xff0c;返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#xff1a;[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]示例 2&#xff1a; 输入&#xff1a…...

UML 顺序图:电子图书馆管理系统的交互之道

目录 一、初识 UML 顺序图 二、电子图书馆管理系统顺序图解析 &#xff08;一&#xff09;借阅流程 &#xff08;二&#xff09;归还流程 三、顺序图绘画 四、顺序图的优势与价值 五、总结 UML 顺序图是描绘系统组件交互的有力工具。顺序图直观展示消息传递顺序与对象协…...

前端渲染pdf文件解决方案-pdf.js

目录 一、前言 二、简介 1、pdf.js介绍 2、插件版本参数 三、通过viewer.html实现预览&#xff08;推荐&#xff09; 1、介绍 2、部署 【1】下载插件包 【2】客户端方式 【3】服务端方式&#xff08;待验证&#xff09; 3、使用方法 【1】预览PDF文件 【2】外部搜索…...

接口测试和功能测试详解

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 本文主要分为两个部分&#xff1a; 第一部分&#xff1a;主要从问题出发&#xff0c;引入接口测试的相关内容并与前端测试进行简单对比&#xff0c;总结两者…...

LeetCode热题100--283.移动零--简单

1.题目 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。 示例 1: 输入: nums [0,1,0,3,12] 输出: [1,3,12,0,0] 示例 2: 输入: nums [0]…...

CoT 数据集如何让大模型学会「一步一步思考」?

目前&#xff0c;大模型的回答路径基本遵循input-output的方式&#xff0c;在面对复杂任务时表现不佳。反之&#xff0c;人类会遵循一套有条理的思维流程&#xff0c;逐步推理得出正确答案。这种差异促使人们深入思考&#xff1a;如何才能让大模型“智能涌现”&#xff0c;学会…...

量子跃迁:Vue组件安全工程的基因重组与生态免疫(完全体)

总章数字免疫系统的解剖学革命 在2024年某国家级数字政务平台的安全审计中&#xff0c;传统前端架构暴露出的信任链断裂问题&#xff0c;导致公民隐私数据以每秒23TB的速度在暗网流通。当我们用PET扫描技术观察现代Web应用的微观结构&#xff0c;发现94.7%的安全威胁源自组件间…...

配置 Nginx 的 HTTPS

证书文件 文件名 作用 来源 example.com.key 服务器的私钥&#xff0c;用于加密和解密数据。 本地生成 -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAqp5c... -----END RSA PRIVATE KEY----- example.com.csr 证书签名请求文件&#xff0c;包含公钥和申请者信息&…...

FPGA开发流程初识

FPGA 的开发流程可知&#xff0c;在 FPGA 开发的过程中会产生很多不同功能的文件&#xff0c;为了方便随时查找到对应文件&#xff0c;所以在开始开发设计之前&#xff0c;我们第一个需要考虑的问题是工程内部各种文件的管理。如 果不进行文件分类&#xff0c;而是将所有文件…...

c++中的enum变量 和 constexpr说明符

author: hjjdebug date: 2025年 04月 23日 星期三 13:40:21 CST description: c中的enum变量 和 constexpr说明符 文章目录 1.Q:enum 类型变量可以有,--操作吗&#xff1f;1.1补充: c/c中enum的另一个细微差别. 2.Q: constexpr 修饰的函数,要求传入的参数必需是常量吗&#xff…...

JVM学习笔记

1、jvm概述 1.1、即时编译 为什么java没有c和c快 多了一层解释&#xff0c;而即时编译就是将热点数放入内存&#xff0c;下次执行的时候不用解释&#xff0c;提高了效率 1.2、常见的jvm jvm不止一个&#xff0c;意不意外&#xff1f;惊不惊喜&#xff1f; 1.3、hotspot的发展…...

AtCoder Beginner Contest 402题解

A - CBC 思路&#xff1a;仔细看这题其实就发现&#xff0c;我们只需要遍历一遍字符串把大写字母输出即可&#xff0c;很标准的签到题 #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. 引言&#xff1a;Rust 语言概述 Rust 是一门专注于性能、内存安全和并发性的现代系统编程语言。自 2010 年由 Mozilla Research 的 Graydon Hoare 发起&#xff0c;并于 2015 年正式发布以来&#xff0c;Rust 凭借其独特的设计理念和强大的功能集&#xff0c;在技术领域迅速…...

HTTP 请求头的 key 不区分大小写。

详细说明 HTTP 协议规范 根据 RFC 7230&#xff0c;HTTP 头字段的名称&#xff08;即 key&#xff09;在传输时不区分大小写。例如&#xff0c;Content-Type 和 content-type 被视为相同的字段。 实际行为 客户端行为&#xff1a;大多数 HTTP 客户端&#xff08;如浏览器、cur…...

4.RabbitMQ - 延迟消息

RabbitMQ延迟消息 文章目录 RabbitMQ延迟消息一、延迟消息介绍二、实现2.1 死信交换机2.2 延迟消息插件2.3 取消超时订单 一、延迟消息介绍 延迟消息&#xff1a;生产者发送消息时指定一个时间&#xff0c;消费者不会立刻收到消息&#xff0c;而是在指定时间后才收到消息 用户…...

C++学习:六个月从基础到就业——STL算法(一) 基础与查找算法

C学习&#xff1a;六个月从基础到就业——STL算法&#xff08;一&#xff09; 基础与查找算法 本文是我C学习之旅系列的第二十五篇技术文章&#xff0c;也是第二阶段"C进阶特性"的第三篇&#xff0c;主要介绍C STL算法库的基础知识与查找类算法。查看完整系列目录了解…...

springboot+vue 支付宝支付(沙箱方式,测试环境使用)

准备工作&#xff1a; 1 支付宝沙箱环境的公钥&#xff0c;私钥配置&#xff0c;查询等使用&#xff0c;如果是用自定义的方式&#xff0c;需要下生成公钥&#xff0c;私钥的工具&#xff0c;否则不需要 登录 - 支付宝 小程序文档 - 支付宝文档中心 2 本地测试时&…...

安装win11自带linux是报错:WslRegisterDistribution failed with error: 0x800701bcErr

确保系统设置中的“打开win11的子系统”已打钩 管理员身份运行cmd&#xff0c;并输入如下 然后再重启ubantu...

面试经历(一)雪花算法

uid生成方面 1&#xff1a;为什么用雪花算法 分布式ID的唯一性需要保证&#xff0c;同时需要做到 1&#xff1a;单调递增 2&#xff1a;确保安全&#xff0c;一个是要能体现出递增的单号&#xff0c;二一个不能轻易的被恶意爬出订单数量 3&#xff1a;含有时间戳 4&#…...

docker底层原理简述

前言 平时用docker很多&#xff0c;今天深入了解下docker原理层面的实现&#xff0c;包括docker核心概念&#xff0c;文件系统&#xff0c;资源隔离&#xff0c;网络通信等 参考文章&#xff1a; Docker底层原理&#xff08;图解秒懂史上最全&#xff09; - 疯狂创客圈 - 博…...

【6D位姿估计】Foundation Pose复现

主要参考 项目仓库README站内其他博文 注意事项 容器化部署不难&#xff0c;主要是部署docker本身会存在一些环境问题&#xff0c;重点关注访问外网的端口需要手动调整至与魔法相同&#xff0c;可以参考&#xff1a; docker部署在启动容器镜像后&#xff0c;需要注意镜像当前…...

TDengine 数据订阅设计

简介 数据订阅作为 TDengine 的一个核心功能&#xff0c;为用户提供了灵活获取所需数据的能力。通过深入了解其内部原理&#xff0c;用户可以更加有效地利用这一功能&#xff0c;满足各种实时数据处理和监控需求。 基本概念 主题 与 Kafka 一样&#xff0c;使用 TDengine 数…...

VMware Fusion Pro 13 Mac版虚拟机 安装Win11系统教程

Mac分享吧 文章目录 Win11安装完成&#xff0c;软件打开效果一、VMware安装Windows11虚拟机1️⃣&#xff1a;准备镜像2️⃣&#xff1a;创建虚拟机3️⃣&#xff1a;虚拟机设置4️⃣&#xff1a;安装虚拟机5️⃣&#xff1a;解决连不上网问题 安装完成&#xff01;&#xff0…...

高并发下单库存扣减异常?飞算 JavaAI 自动化生成分布式事务解决方案

在电商、旅游等行业业务量激增&#xff0c;高并发下单场景中&#xff0c;传统库存扣减方式弊端尽显。超卖问题因缺乏有效并发控制机制频发&#xff0c;多个订单同时访问库存数据&#xff0c;导致同一商品多次售出&#xff0c;订单无法履约引发客户投诉&#xff1b;同时&#xf…...

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外边距合并现象

外边距合并&#xff08;Margin Collapsing&#xff09;是指在文档流中&#xff0c;两个或多个相邻元素的外边距&#xff08;margin&#xff09;会合并为一个外边距&#xff0c;其大小会取其中最大的外边距值 当两个相邻的兄弟元素之间没有其他内容&#xff08;如边框、内边距、…...

《深度神经网络之数据增强、模型保存、模型调用、学习率调整》

文章目录 前言一、数据增强1、什么是数据增强&#xff1f;2、数据增强的实现方法&#xff08;1&#xff09;几何变换翻转:旋转&#xff1a;平移&#xff1a; &#xff08;2&#xff09;颜色变换亮度调整&#xff1a;对比度调整&#xff1a;色彩抖动&#xff1a; &#xff08;3&…...

【Java学习笔记】random的使用

random使用方法 使用说明&#xff1a;返回的是(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类型使用

第一步&#xff1a;添加缓存 以若依岗位代码为例 一&#xff1a;首先从redis中查询岗位信息&#xff0c;如果查询到了则直接返回。 二&#xff1a;如果redis中没有数据&#xff0c;则直接从数据库中查询。查询后放到redis并返回 package com.ruoyi.system.service.impl;imp…...

AIGC架构与原理

AIGC&#xff08;AI Generated Content&#xff0c;人工智能生成内容&#xff09;的架构与原理 AIGC通过整合数据采集、模型训练、推理服务等模块&#xff0c;结合深度学习与生成对抗网络&#xff08;GAN&#xff09;等技术&#xff0c;实现从数据到内容的自动化生成。 一、AIG…...

安全复健|windows常见取证工具

写在前面&#xff1a; 此博客仅用于记录个人学习内容&#xff0c;学识浅薄&#xff0c;若有错误观点欢迎评论区指出。欢迎各位前来交流。&#xff08;部分材料来源网络&#xff0c;若有侵权&#xff0c;立即删除&#xff09; 取证 01系统运行数据 使用工具&#xff1a;Live-F…...

Oracle EBS R12.2 汉化

一、前言 在使用oracle ebs时&#xff0c;使用中文会更好的理解整个ebs流程&#xff0c;以下介绍oracle r12中文补丁的方式 如果你的系统除了支持英语外&#xff0c;还支持其他语言&#xff0c;比如中文&#xff0c;那你在下载补丁的时候除了下载Generic Platform版本外&#…...

【Java面试笔记:基础】12.Java有几种文件拷贝方式?哪一种最高效?

在 Java 中,文件拷贝可以通过多种方式实现,不同方式的性能和适用场景有所差异。 1. Java 文件拷贝方式 传统 IO 方式 使用 FileInputStream 和 FileOutputStream,通过循环读取和写入数据实现文件拷贝。 示例代码: try (InputStream is = new FileInputStream("sou…...

互联网大厂Java面试:RocketMQ、RabbitMQ与Kafka的深度解析

互联网大厂Java面试&#xff1a;RocketMQ、RabbitMQ与Kafka的深度解析 面试场景 面试官&#xff1a;马架构&#xff0c;您好&#xff01;欢迎参加我们的面试。今天我们将围绕消息中间件展开讨论&#xff0c;尤其是RocketMQ、RabbitMQ和Kafka。您有十年的Java研发和架构设计经…...

kali安装切换jdk1.8.0_451java8详细教程

kali安装切换jdk1.8.0_451java8详细教程 下载链接&#xff1a; jdk-8u451-linux-i586.tar.gz 链接: https://pan.baidu.com/s/1lpgI0JMfHpZ__RxsF8UoBw?pwdx3z2 提取码: x3z2 解压jdk 首先将下载好的压缩包放在kali虚拟机中&#xff0c;一般是直接拖到桌面 然后cd到压缩包…...

众趣科技X世界读书日丨数字孪生技术赋能图书馆空间智慧化运营

4月23日&#xff0c;是第30个“世界读书日”&#xff0c;不仅是庆祝阅读的日子&#xff0c;更是思考知识传播未来的契机。 图书馆作为主要传播图书的场所&#xff0c;在科技的发展中&#xff0c;图书馆正面临前所未有的挑战&#xff0c;联合国数据显示&#xff0c;全球近30%的…...

Python内置函数-aiter()

Python内置函数 aiter() 用于获取异步可迭代对象的异步迭代器&#xff0c;是异步编程中的核心工具之一。 1. 基本概念 异步可迭代对象&#xff1a;实现了 __aiter__() 和 __anext__() 方法的对象&#xff0c;支持 async for 循环。 异步迭代器&#xff1a;通过 aiter() 获取的…...

Java 实现单链表翻转(附详细注释)

1. 引言 单链表&#xff08;Singly Linked List&#xff09;是一种常见的数据结构&#xff0c;在算法和数据结构的学习中占有重要地位。翻转单链表是一道经典的面试题&#xff0c;本文将介绍几种常见的 Java 实现方法&#xff0c;并详细讲解关键步骤的含义。 2. 单链表定义 …...

基于HPC的气候模拟GPU加速实践全流程解析

基于HPC的气候模拟GPU加速实践全流程解析 关键词&#xff1a;气候模型、GPU加速、CUDA编程、性能优化、分布式训练 摘要&#xff1a; 本文针对全球气候模拟中10^12级网格点实时计算需求&#xff0c;提出基于CUDA的并行计算架构。通过改进WRF模式的分块矩阵乘法算法&#xff0c…...

【初级】前端开发工程师面试100题(一)

本题库共计包含100题,考察html,css,js,以及react,vue,webpack等基础知识掌握情况。 HTML基础篇 说说你对HTML语义化的理解? 语义化就是用合适的标签表达合适的内容,比如<header>表示页眉,<nav>表示导航。这样不仅代码更清晰,对SEO也友好,屏幕阅读器也能…...

大模型框架技术演进与全栈实践指南

‌一、大模型框架概述 ‌大模型框架‌是支撑大规模语言模型&#xff08;LLM&#xff09;训练、推理和应用开发的核心技术体系&#xff0c;涵盖分布式训练、高效推理、应用编排等全流程。从AlphaGo到GPT-4&#xff0c;大模型框架的进化推动AI从实验室走向工业化落地。据IDC预测…...

【Bug】 [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed

当你在进行深度学习相关操作时&#xff0c;若因缺少本地的 CA 证书而无法下载资源&#xff0c;下面为你介绍几种解决办法&#xff1a; 方法一&#xff1a;更新 CA 证书 在大多数 Linux 发行版中&#xff0c;你可以使用包管理器来更新 CA 证书。例如&#xff0c;在基于 Debian…...

第七章:Workspace Security

Chapter 7: Workspace Security 从变形金刚到安全防护罩&#xff1a;如何为代理设置权限边界&#xff1f; 在上一章多后端配置&#xff0c;我们学会了让代理像变形金刚一样切换不同环境。但就像超级英雄需要遵守法律一样&#xff0c;代理也需要一个“安全防护罩”来限制它的操…...

【论文阅读】Hierarchical Group-Level Emotion Recognition

【论文阅读】Hierarchical Group-Level Emotion Recognition 摘要1.介绍2.相关工作3.方法4.实验5.分析 摘要 本篇博客参考IEEE于2021年收录的论文Hierarchical Group-Level Emotion Recognition&#xff0c;对其主要内容进行总结&#xff0c;以便加深理解和记忆 1.介绍 1&am…...

(2025最新版)CUDA安装及环境配置

CUDA安装 文章目录 CUDA安装检查本地环境下载CUDA安装包CUDA安装检查是否安装成功 学习深度学习的小伙伴在配置环境的时候必不可少的一件事就是安装CUDA&#xff0c;在这个过程中也是容易踩很多坑&#xff0c;所以这里写一篇教程来帮助新入门的小伙伴快速安装CUDA&#xff0c;减…...

ODC 4.3.4 发布:三大核心功能升级,打造更好的数据开发体验

ODC 是OceanBase提供的企业级数据库协同开发平台&#xff0c;提供了团队协作开发的基础框架&#xff0c;和14种工单任务类型。此次升级的 ODC 4.3.4版本&#xff0c;重点优化了30余项功能&#xff0c;主要聚焦快速上手、配置管理和核心功能中的改进&#xff0c;来为用户打造更高…...

JavaFX 第一篇 Hello World

1、简介 JavaFX 是一个用于构建客户端应用程序的 Java 库&#xff0c;作为 Java 标准库的一部分&#xff08;JDK 8 到 10&#xff09;&#xff0c;从 JDK 11 开始&#xff0c;JavaFX 将以独立模块发布&#xff0c;将不再包含在 JDK标准库中&#xff0c;他是 Java 应用程序开发的…...

es的range失效

es的range失效的解决方法 问题描述 当我们es使用keyword类型存储数字时&#xff0c;当我们使用range时我们发现range失效的问题&#xff0c;例如以下的用例&#xff1a; 我们创建一个test1的索引test1&#xff1a; 使用_bulk进行批量导入数据&#xff1a; 进行查询我们发现我…...

gem5-gpu教程03 当前的gem5-gpu软件架构(因为涉及太多专业名词不知道该如何翻译所以没有汉化)

Current gem5-gpu Software Architecture 这是当前gem5-gpu软件架构的示意图。 CudaCore (src/gpu/gpgpu-sim/cuda_core.*, src/gpu/gpgpu-sim/CudaCore.py) Wrapper for GPGPU-Sim shader_core_ctx (gpgpu-sim/gpgpu-sim/shader.h) Sends instruction, global and const m…...