1. 背景-什么是限流?
限流,顾名思义,其目的就是要限制流量,将其控制在一个合理的范围内,以免大流量对网络或服务器造成压力。
为了达到限流的目的,可以从两个方向着手:
-
限制【速率】
通过控制请求资源的速率,来达到限流的目的。如限制每个访问资源的请求间的时间间隔为200ms。
-
限制【数量】
通过限制允许落到资源上的请求的个数来达到限流的目的。比如我们限制1秒内只允许该资源被请求5次,则QPS=5.
这两种方式的区别或联系如下:
- 限制速率,通常是将请求达到资源的速度变为匀速,或者说,每个到达资源的请求之间的时间间隔是固定的。如限制每个访问资源的请求间的时间间隔为200ms。
- 限制请求数量,则只关心指定时间内可以访问资源的请求数量是多少,而不限制请求的速率。如1秒内限制某资源只能被访问5次,则在这一秒内,可能在前100毫秒里就来了5个请求,而后面的900毫秒内的请求就会被拒绝。
- 限制了请求速率,也就间接的限制了一段时间内的请求量。如限制每个请求的间隔为200ms,则1秒内的请求量就是 1s / 200ms = 5个。
2. 限流的方案
常见的限流方案为漏桶(Leaky Bucket)和令牌桶(Token Bucket)。
3. 限流的实现
基于令牌桶,参考Guava RateLimiter的SmoothBursty实现。
3.1 限流器的使用
Guava RateLimiter是基于令牌桶思想实现的限流器,我们看看如何使用。
使用一个死循环打印一句话。在没有限流的时候,控制台会疯狂打印。
public class RateLimiterDemo {
public static void main(String[] args) {
while (true) {
System.out.println("hello world" + System.currentTimeMillis());
}
}
}
此时我们希望限制1秒打印一次,借助RateLimiter可以方便的实现。
public class RateLimiterDemo {
public static void main(String[] args) {
// 调用静态工厂方法,获得RateLimiter实例。传入的参数是期望的QPS
// 我们希望1秒打印一次,即QPS=1
RateLimiter rateLimiter = RateLimiter.create(1.0);
while (true) {
// 申请获取令牌
// 若获取成功,则请求通过向下执行;若失败,则当前线程阻塞等待。
rateLimiter.acquire();
System.out.println("hello world -- " + System.currentTimeMillis());
}
}
}
输出结果:
hello world -- 1590378303960
hello world -- 1590378304960
hello world -- 1590378305963
可见打印已经是1秒一次,限流成功。
3.2 基于令牌桶思想实现限流器
刚才我们使用Guava RateLimiter实现了限流的需求,现在我们将仿照其实现,自己写一个限流器出来,为了方便对应Guava的代码学习,我们将变量、方法都直接沿用Guava RateLimiter的命名。
在这之前,先考虑几个问题,这些问题都会在后续实现的过程中逐一解答:
- 令牌桶如何用代码表示?
- 令牌的生成如何实现?
- 令牌桶的一个特点是支持突发流量,如何实现?
新建TokenBucketRateLimiter类。
public class TokenBucketRateLimiter {
}
我们首先先将目光转移到上图的令牌桶的位置,从图中可知令牌桶应具备的几个属性:
- 生成令牌的速率,即每过多长时间生成一个新令牌。
- 令牌桶的容量,即最多存储多少块令牌。
- 令牌桶中当前剩余的令牌数量。
除了令牌桶自身的三个属性外,我们还需要一个计时器和一个表示“无需等待即可获得令牌的时间”的一个时间戳。
除此之外,令牌桶需要支持突发流量,我们可以指定允许突发流量持续的时间。
所以我们一共需要6个属性,就可以表示出一个令牌桶。
/**
* 令牌桶的容量,即最多存储多少块令牌
*/
private double maxPermits;
/**
* 令牌桶中当前剩余的令牌数
*/
private double storedPermits;
/**
* 每隔多长时间生成一块令牌。单位:微秒
* coolDown表示冷却时间
* (游戏中技能释放后需要等待一段时间后才能重新释放,这段时间被成为冷却时间)
*/
private double coolDown;
/**
* 无需等待即可获得令牌的时间戳,单位微秒
*/
private long nextFreePermit