Guava RateLimter 基础学习
- 平滑突发限流
- 平滑预热限流
- 原理分析--以平滑突发限流为例
平滑突发限流
public static void main(String[] args) {RateLimiter rateLimiter = RateLimiter.create(5);while (true) {double acquire = rateLimiter.acquire();System.out.println(acquire);}}/**0.00.1987560.194320.199067*/
可见 令牌并不是一次性给的,而是在这一秒中,按照固定的速率分发的,所以每一个请求大概0.2s。不过,RateLimiter有积累的作用,在桶还没有满的情况下,RateLimiter会一直分发,直到桶被装满位置,这时候如果来了请求,只要令牌还没有被分发完,就可以立即获得令牌,所以RateLimiter可以应对突发性流量。
public static void main(String[] args) throws InterruptedException {RateLimiter rateLimiter = RateLimiter.create(5);Thread.sleep(1000);while (true) {double acquire = rateLimiter.acquire();System.out.println(acquire);}}/**0.00.00.00.00.00.00.1986760.194248*/
RateLimiter还有一个特性,在没有足够令牌发放时,采用滞后处理的方式;比如,现在只有3个令牌,但是现在有一个请求要4个令牌,可以立即响应,先跟RateLimiter借1个令牌。这个请求获取令牌所需等待的时间由下一次请求来承受,也就是代替前一个请求进行等待。
public static void main(String[] args) throws InterruptedException {RateLimiter rateLimiter = RateLimiter.create(6);Thread.sleep(1000);System.out.println(rateLimiter.acquire(3));System.out.println(rateLimiter.acquire(4)); //令牌不够,但是仍然能立即返回System.out.println(rateLimiter.acquire(1));}/**0.0 0.0 0.163041*/
平滑预热限流
我们先来看一下源码
//permitsPerSecond请求的令牌个数,warmupPeriod预热的时间,unit预热时间间隔单位public static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit) {Preconditions.checkArgument(warmupPeriod >= 0L, "warmupPeriod must not be negative: %s", new Object[]{warmupPeriod});return create(RateLimiter.SleepingStopwatch.createFromSystemTimer(), permitsPerSecond, warmupPeriod, unit);}
带有预热期的平滑限流,他启动后会有一段预热期,发放令牌的频率和时间呈一个梯度,等到时间过了warmupPeriod,才到达原本设置的频率。这种功能主要适合系统刚启动需要一点时间来“热身”的场景。
RateLimiter rateLimiter = RateLimiter.create(6, 2, TimeUnit.SECONDS);while (true) {System.out.println(rateLimiter.acquire(2));}/**0.00.888101 0.6601520.439506 上面加起来刚好差不多2s0.328413 热身完毕0.3295040.329148 */
原理分析–以平滑突发限流为例
我们先从acquire看起
public double acquire() {return this.acquire(1);}public double acquire(int permits) {// 返回现在需要等待的时间long microsToWait = this.reserve(permits);//睡眠 阻塞this.stopwatch.sleepMicrosUninterruptibly(microsToWait);//返回等待的时间 转换成秒return 1.0D * (double)microsToWait / (double)TimeUnit.SECONDS.toMicros(1L); }final long reserve(int permits) {checkPermits(permits); //检查permits是否合理 >0synchronized(this.mutex()) { //lockreturn this.reserveAndGetWaitLength(permits, this.stopwatch.readMicros()); //返回需要等待的时间//this.stopwatch.readMicros() 现在的时间 }}final long reserveAndGetWaitLength(int permits, long nowMicros) {long momentAvailable = this.reserveEarliestAvailable(permits, nowMicros); //算出距离现在最近的能够获取令牌的时间return Math.max(momentAvailable - nowMicros, 0L);//返回需要等待的时间}
接下来我们来看一下reserveEarliestAvailabe是怎么算出最近能够获取到令牌的时间
final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {// 刷新令牌数,相当于每次acquire的时候才根据时间进行令牌的刷新resync(nowMicros);//nextFreeTicketMicros 下一次请求可以获取令牌的时间 成员属性long returnValue = nextFreeTicketMicros;// 获取当前已有的令牌数和需要获取的目标令牌数进行比较,计算出可以目前即可得到的令牌数。double storedPermitsToSpend = min(requiredPermits, this.storedPermits);// freshPermits是需要预先支付的令牌,也就是目标令牌数减去目前即可得到的令牌数// 因为会突然涌入大量请求,而现有令牌数又不够用,因此会预先支付一定的令牌数double freshPermits = requiredPermits - storedPermitsToSpend;// waitMicros即是产生预先支付令牌的数量时间,则将下次要添加令牌的时间应该计算时间加上watiMicros//stableIntervalMicros 添加令牌的时间间隔//SmoothBuresty的storedPermitsToWaitTime直接返回0,所以watiMicros就是预先支付的令牌所需等待的时间 但是在SmoothWarmingUp中就不一样,用于实现预热缓冲期long waitMicros = storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)+ (long) (freshPermits * stableIntervalMicros);try {// 更新nextFreeTicketMicros,本次预先支付的令牌所需等待的时间让下一次请求来实际等待。this.nextFreeTicketMicros = LongMath.checkedAdd(nextFreeTicketMicros, waitMicros);} catch (ArithmeticException e) {this.nextFreeTicketMicros = Long.MAX_VALUE;}// 更新令牌数,最低数量为0this.storedPermits -= storedPermitsToSpend;// 返回旧的nextFreeTicketMicros数值,无需为预支付的令牌多加等待时间。return returnValue;
}// SmoothBurest
long storedPermitsToWaitTime(double storedPermits, double permitsToTake) {return 0L;
}//更新令牌数
void resync(long nowMicros) {// 当前时间晚于nextFreeTicketMicros,所以刷新令牌和nextFreeTicketMicrosif (nowMicros > nextFreeTicketMicros) {// coolDownIntervalMicros函数获取每几秒生成一个令牌,SmoothWarmingUp和SmoothBuresty的实现不同// SmoothBuresty的coolDownIntervalMicros直接返回stableIntervalMicros// 当前时间减去要更新令牌的时间获取时间间隔,再除以添加令牌时间间隔获取这段时间内要添加的令牌数storedPermits = min(maxPermits,storedPermits+ (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros());nextFreeTicketMicros = nowMicros;}// 如果当前时间早于nextFreeTicketMicros,则获取令牌的线程要一直等待到nextFreeTicketMicros,该线程获取令牌所需// 额外等待的时间由下一次获取的线程来代替等待。
}
//返回生成令牌的时间间隔
double coolDownIntervalMicros() {return stableIntervalMicros;
}