一、定义
RateLimiter使用的是一种叫令牌桶的流控算法,RateLimiter会按照一定的频率往桶里扔令牌,线程拿到令牌才能执行,比如你希望自己的应用程序QPS不要超过1000,那么RateLimiter设置1000的速率后,就会每秒往桶里扔1000个令牌。
package com.google.common.util.concurrent;
@ThreadSafe
@Beta
@GwtIncompatible
public abstract class RateLimiter {
RateLimiter经常用于限制对一些物理资源或者逻辑资源的访问速率。
通过设置许可证的速率来定义RateLimiter。在默认配置下,许可证会在固定的速率下被分配,速率单位是每秒多少个许可证。为了确保维护配置的速率,许可会被平稳地分配,许可之间的延迟会做调整。
可能存在配置一个拥有预热期的RateLimiter 的情况,在这段时间内,每秒分配的许可数会稳定地增长直到达到稳定的速率。
例 一:
我们需要处理一个任务列表,但我们不希望每秒的任务提交超过3个:
//速率是每秒3个许可
final RateLimiter rateLimiter = RateLimiter.create(3.0);
void submitTasks(List tasks, Executor executor) {
for (Runnable task : tasks) {
rateLimiter.acquire(); // 也许需要等待
executor.execute(task);
}
}
例 二:
处理一个数据流,并希望以每秒5kb的速率处理它。可以通过要求每个字节代表一个许可,然后指定每秒5000个许可来完成:
// 每秒5000个许可
final RateLimiter rateLimiter = RateLimiter.create(5000.0);
void submitPacket(byte[] packet) {
rateLimiter.acquire(packet.length);
networkService.send(packet);
}
有一点很重要,那就是请求的许可数从来不会影响到请求本身的限制(调用acquire(1) 和调用acquire(1000) 将得到相同的限制效果,如果存在这样的调用的话),但会影响下一次请求的限制,也就是说,如果一个高开销的任务抵达一个空闲的RateLimiter,它会被马上许可,但是下一个请求会经历额外的限制,从而来偿付高开销任务。注意:RateLimiter 并不提供公平性的保证。
二、接口
返回数据类型 | 方法 | 和描述 |
---|---|---|
double | acquire() | 从RateLimiter获取一个许可,该方法会被阻塞直到获取到请求 |
double | acquire(int permits) | 从RateLimiter获取指定许可数,该方法会被阻塞直到获取到请求 |
static RateLimiter | create(double permitsPerSecond) | 根据指定的稳定吞吐率创建RateLimiter,这里的吞吐率是指每秒多少许可数(通常是指QPS,每秒多少查询) |
static RateLimiter | create(double permitsPerSecond, long warmupPeriod, TimeUnit unit) | 根据指定的稳定吞吐率和预热期来创建RateLimiter,这里的吞吐率是指每秒多少许可数(通常是指QPS,每秒多少个请求量),在这段预热时间内,RateLimiter每秒分配的许可数会平稳地增长直到预热期结束时达到其最大速率。(只要存在足够请求数来使其饱和) |
double | getRate() | 返回RateLimiter 配置中的稳定速率,该速率单位是每秒多少许可数 |
void | setRate(double permitsPerSecond) | 更新RateLimite的稳定速率,参数permitsPerSecond 由构造RateLimiter的工厂方法提供 |
String | toString() | 返回对象的字符表现形式 |
boolean | tryAcquire() | 从RateLimiter 获取许可,如果该许可可以在无延迟下的情况下立即获取得到的话 |
boolean | tryAcquire(int permits) | 从RateLimiter 获取许可数,如果该许可数可以在无延迟下的情况下立即获取得到的话 |
boolean | tryAcquire(int permits, long timeout, TimeUnit unit) | 从RateLimiter 获取指定许可数如果该许可数可以在不超过timeout的时间内获取得到的话,或者如果无法在timeout 过期之前获取得到许可数的话,那么立即返回false (无需等待) |
boolean | tryAcquire(long timeout, TimeUnit unit) | 从RateLimiter 获取许可如果该许可可以在不超过timeout的时间内获取得到的话,或者如果无法在timeout 过期之前获取得到许可的话,那么立即返回false(无需等待) |
三、总结
1、为什么需要预热期?
答:RateLimiter每秒分配的许可数会平稳地增长直到预热期结束时达到其最大速率,这样最大请求量就是,预热器结束时候的量。
2、怎么保证稳定分配令牌 ?
答:采用的是滑动时间窗口,可以避免时间段交替的时候,出现获取token不稳定的情况。
3、怎么保证线程安全 ?
答:源代码中通过synchronized(this)保证线程安全,应该有更高效的额线程安全实现方案,比如:AQS
部分内容摘自:http://ifeve.com/guava-ratelimiter/