Guava RateLimter 基础学习
平滑突发限流
public static void main(String[] args) {
RateLimiter rateLimiter = RateLimiter.create(5);
while (true) {
double acquire = rateLimiter.acquire();
System.out.println(acquire);
}
}
/**
0.0
0.198756
0.19432
0.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.0
0.0
0.0
0.0
0.0
0.0
0.198676
0.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.0
0.888101
0.660152
0.439506 上面加起来刚好差不多2s
0.328413 热身完毕
0.329504
0.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是否合理 >0
synchronized(this.mutex()) { //lock
return 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;
}
// 更新令牌数,最低数量为0
this.storedPermits -= storedPermitsToSpend;
// 返回旧的nextFreeTicketMicros数值,无需为预支付的令牌多加等待时间。
return returnValue;
}
// SmoothBurest
long storedPermitsToWaitTime(double storedPermits, double permitsToTake) {
return 0L;
}
//更新令牌数
void resync(long nowMicros) {
// 当前时间晚于nextFreeTicketMicros,所以刷新令牌和nextFreeTicketMicros
if (nowMicros > nextFreeTicketMicros) {
// coolDownIntervalMicros函数获取每几秒生成一个令牌,SmoothWarmingUp和SmoothBuresty的实现不同
// SmoothBuresty的coolDownIntervalMicros直接返回stableIntervalMicros
// 当前时间减去要更新令牌的时间获取时间间隔,再除以添加令牌时间间隔获取这段时间内要添加的令牌数
storedPermits = min(maxPermits,
storedPermits
+ (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros());
nextFreeTicketMicros = nowMicros;
}
// 如果当前时间早于nextFreeTicketMicros,则获取令牌的线程要一直等待到nextFreeTicketMicros,该线程获取令牌所需
// 额外等待的时间由下一次获取的线程来代替等待。
}
//返回生成令牌的时间间隔
double coolDownIntervalMicros() {
return stableIntervalMicros;
}
缺点
- RateLimiter只能运用与单机不能运用与分布式
- 结合redis实现分布式
结合Redis实现分布式
思路
- 自己按照这个原理写一个锁,把字段放在redis里面