令牌桶算法与Guava的实现RateLimiter源码分析
令牌桶RateLimiter
简介
令牌桶算法是一种限流算法。
令牌桶算法的原理就是以一个恒定的速度往桶里放入令牌,每一个请求的处理都需要从桶里先获取一个令牌,当桶里没有令牌时,则请求不会被处理,要么排队等待,要么降级处理,要么直接拒绝服务。当桶里令牌满时,新添加的令牌会被丢弃或拒绝。
令牌桶算法主要是可以控制请求的平均处理速率,它允许预消费,即可以提前消费令牌,以应对突发请求,但是后面的请求需要为预消费买单(等待更长的时间),以满足请求处理的平均速率是一定的。
RateLimiter使用示例
导入maven依赖
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>29.0-jre</version>
</dependency>
编写测试代码
public class RateLimiterTest {
public void limit() {
// 创建一个限流器,设置每秒放置的令牌数为1个
RateLimiter rateLimiter = RateLimiter.create(1);
IntStream.range(1, 10).forEach(i -> {
// 一次获取i个令牌
double waitTime = rateLimiter.acquire(i);
System.out.println("acquire:" + i + " waitTime:" + waitTime);
});
}
public static void main(String[] args) {
RateLimiterTest rateLimiterTest = new RateLimiterTest();
rateLimiterTest.limit();
}
}
这段代码创建一个限流器,设置每秒放置的令牌数为1个,并循环获取令牌,每次获取i个。
执行结果:
第一次获取一个令牌时,等待0s立即可获取到(这里之所以不需要等待是因为令牌桶的预消费特性),第二次获取两个令牌,等待时间1s,这个1s就是前面获取一个令牌时因为预消费没有等待延到这次来等待的时间,这次获取两个又是预消费,所以下一次获取(取3个时)就要等待这次预消费需要的2s了,依此类推。可见预消费不需要等待的时间都由下一次来买单,以保障一定的平均处理速率(上例为1s一次)。
RateLimiter的实现
RateLimiter类在guava里是一个抽象类,其有两个具体实现:
- SmoothBursty(平滑突发):以恒定的速率生成令牌。
- SmoothWarmingUp(顺利热身):令牌生成的速度逐渐提升,直到达到一个稳定的值。
其类图如下:
他们的关系与作用:
- RateLimiter是顶层封装,提供新建令牌桶的方法
- SleepingStopwatch是guava实现的一个时钟类,提供时钟功能
- SmoothRateLimiter是令牌桶抽象,提供操作令牌桶的抽象方法,其有两个内部类SmoothBursty和SmoothWarmingUp
- SmoothBursty是恒定速率令牌桶实现
- SmoothWarmingUp是逐渐加速知道稳定的令牌桶实现
源码解析
SmoothRateLimiter
SmoothRateLimiter是令牌桶抽象,其有四个关键的属性:
/** 当前存储的许可证。 */
double storedPermits;
/** 存储许可证的最大数量。 */
double maxPermits;
/**
* 两个单位请求之间的间隔,以我们的稳定速率。
* 例如,每秒 5 个许可的稳定速率具有 200 毫秒的稳定间隔。
*/
double stableIntervalMicros;
/**
* 授予下一个请求(无论其大小如何)的时间。
* 在批准请求后,这将在将来进一步推送。大请求比小请求更进一步。
*/
private long nextFreeTicketMicros = 0L