限流算法-令牌桶算法:
如上图所示,随着时间流逝,系统会按恒定1/QPS时间间隔(如果QPS=100,则间隔是10ms)往桶里加入Token,如果桶已经满了就不再加了.
新请求来临时,会各自拿走N(N≥1)个Token,如果没有Token可拿了就阻塞或者拒绝服务.
由上述算法原理描述,我们可以得出,限制速率的方式有两种,
1) 控制令牌生成的速率;
2) 控制消耗令牌的速率;
基于令牌桶算法,google开源工具包提供了一个限流工具类RateLimiter。RateLimiter经常用于限制对一些物理资源或者逻辑资源的访问速率.它支持两种获取permits接口,一种是如果拿不到立刻返回false,一种会阻塞等待一段时间看能不能拿到,后续我们会针对这两种接口进行阐述 。
RateLimiter工具类:
写在最前:
相关pom依赖:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>27.0.1-jre</version>
</dependency>
接口定义:(加粗的为日常用到的方法)
接口名称
|
接口释义
|
---|---|
static RateLimiter create(double permitsPerSecond) | 根据指定的稳定吞吐率创建RateLimiter,这里的吞吐率是指每秒多少许可数(通常是指QPS,每秒多少查询)(设定速率的地方之一) |
static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit) | 根据指定的稳定吞吐率和预热期来创建RateLimiter,这里的吞吐率是指每秒多少许可数(通常是指QPS,每秒多少个请求量),在这段预热时间内,RateLimiter每秒分配的许可数会平稳地增长直到预热期结束时达到其最大速率。(只要存在足够请求数来使其饱和)(该方法用于某些数据或请求需要预热的场景)参数含义 :qps,预热时长 ,预热时长的时间单位 |
double acquire() | 从RateLimiter获取一个许可,该方法会被阻塞直到获取到请求(默认是1)(请求会阻塞 ) |
double acquire(int permits) | 从RateLimiter获取指定许可数,该方法会被阻塞直到获取到请求(设定速率的地方之一,请求会阻塞 ) |
boolean tryAcquire() | 从RateLimiter 获取许可,如果该许可可以在无延迟下的情况下立即获取得到的话(默认是1)(不会阻塞请求,获取不到token,直接返回失败,适用于允许快速失败的场景) |
boolean tryAcquire(int permits) | 同tryAcquire(),获取N个许可 |
boolean tryAcquire(long timeout, TimeUnit unit) | 从RateLimiter 获取许可如果该许可可以在不超过timeout的时间内获取得到的话,或者如果无法在timeout 过期之前获取得到许可的话,那么立即返回false(无需等待)(非阻塞,超出等待时间立即返回失败) |
boolean tryAcquire(int permits, long timeout, TimeUnit unit) | 同tryAcquire(long timeout, TimeUnit unit) |
double getRate() | 返回RateLimiter 配置中的稳定速率,该速率单位是每秒多少许可数 |
void setRate(double permitsPerSecond) | 更新RateLimite的稳定速率,参数permitsPerSecond 由构造RateLimiter的工厂方法提供。 |
String toString() | 返回对象的字符表现形式 |
方法使用示例:
第一步:声明RateLimiter对象
public static final RateLimiter limiter = RateLimiter.create(500.0); //qps:500
或者
public static final RateLimiter limiter = RateLimiter.create(500.0,10,TimeUnit.SECONDS); //qps:500 预热期:10S(在10S内,qps逐步上升到500/s)
第二步:在需要进行速率限制的地方,获取令牌
for (Runnable task : tasks) {
limiter.acquire(); // 需要等待(请求会被阻塞 ,直到获取到令牌才执行)
xxxxxx;//执行任务逻辑
}
或者
for (Runnable task : tasks) {
boolean result = limiter.tryAcquire(); // 不需要等待(请求不会阻塞 )
if(result){
xxxxxx; //获取到令牌,执行相关任务逻辑
}else{
continue; //未获取到令牌,快速失败
}
}
以上是个人对RateLimiter工具类在日常项目中的一些理解和使用,如有不正确的地方,欢迎指正 ,也可自行完善/修改文中内容。
参考资料:
- https://google.github.io/guava/releases/19.0/api/docs/index.html?com/google/common/util/concurrent/RateLimiter.html(官方API)
- http://ifeve.com/guava-ratelimiter/ (官方API 译文)