Guava之RateLimiter速率限制器
在平时开发中,我们有时需要对某些接口的调用进行速率限制,以保护系统的稳定运行。
常用的限流算法有漏桶算法和令牌桶算法,guava的RateLimiter使用的是令牌桶算法,也就是以固定的频率向桶中放入令牌,例如一秒钟10枚令牌,实际业务在每次响应请求之前都从桶中获取令牌,只有取到令牌的请求才会被成功响应,获取的方式有两种:阻塞等待令牌或者取不到立即返回失败

1. 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(无需等待) |
2. 实例
我们需要处理一个任务列表,但我们不希望每秒的任务提交超过两个:
1
2
3
4
5
6
7
8
9//速率是每秒两个许可
final RateLimiter rateLimiter = RateLimiter.create(2.0);
void submitTasks(List tasks, Executor executor) {
for (Runnable task : tasks) {
rateLimiter.acquire(); // 该方法会被阻塞直到获取到请求
executor.execute(task);
}
}某个Controller请求,限制每秒5个
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class HelloController {
//每秒只发出5个令牌
private static final RateLimiter rateLimiter = RateLimiter.create(5.0);
private AccessLimitService accessLimitService;
public String access(){
//尝试获取令牌
if(rateLimiter.tryAcquire()){
//执行业务
return "aceess success [" + sdf.format(new Date()) + "]";
}else{
//请求数超出
return "aceess limit [" + sdf.format(new Date()) + "]";
}
}
}一直被调用的接口,每30秒打印一次日志
1 | private static final RateLimiter rateLimiter = RateLimiter.create(1.0); |