如果你还不了解RateLimiter的用法,可以参考 Guava官方文档-RateLimiter类
常用的限流算法
- 漏桶算法 请求先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大会直接溢出,可以看出漏桶算法能强行限制数据的传输速率。
- 令牌桶算法 系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。
RateLimiter 基本用法
@Test
public void rateLimiterTest() {
RateLimiter rateLimiter = RateLimiter.create(0.5);
int[] acquire = {1, 6, 2};
for (int i = 0; i < acquire.length; i++) {
System.out.println(System.currentTimeMillis() + "acq " + acquire[i] + ": wait " + rateLimiter.acquire(acquire[i]) + "s");
}
}
结果如下
1561465268189acq 1: wait 0.0s
1561465268190acq 6: wait 1.998421s
1561465270191acq 2: wait 11.996964s
观察发现,RateLimiter 有预消费的能力
Guava有两种限流模式
- 稳定模式(SmoothBursty:令牌生成速度恒定)
- 渐进模式(SmoothWarmingUp:令牌生成速度缓慢提升直到维持在一个稳定值)
SmoothBursty.java 几个关键参数
/**
* The currently stored permits.
* 当前存储的令牌数
*/
double storedPermits;
/**
* The maximum number of stored permits.
* 最大能存储的令牌数
*/
double maxPermits;
/**
* The interval between two unit requests, at our stable rate. E.g., a stable rate of 5 permits
* per second has a stable interval of 200ms.
* 添加令牌的时间间隔
*/
double stableIntervalMicros;
/**
* The time when the next request (no matter its size) will be granted. After granting a
* request, this is pushed further in the future. Large requests push this further than small
* requests.
* 下一次请求可以获取令牌的起始时间
* 由于RateLimiter允许预消费,上次请求预消费令牌后
* 下次请求需要等待相应的时间到nextFreeTicketMicros时刻才可以获取令牌
*/
private long nextFreeTicketMicros = 0L; // could be either in the past or future
几个关键函数
private void resync(long nowMicros) {
// if nextFreeTicket is in the past, resync to now
if (nowMicros > nextFreeTicketMicros) {
storedPermits = min(maxPermits,storedPermits + (nowMicros - nextFreeTicketMicros) / stableIntervalMicros);
nextFreeTicketMicros = nowMicros;
}
}
根据令牌桶算法,桶中的令牌是持续生成存放的,有请求时需要先从桶中拿到令牌才能开始执行,谁来持续生成令牌存放呢?
开启一个定时任务,由定时任务持续生成令牌。这样的问题在于会极大的消耗系统资源,如,某接口需要分别对每个用户做访问频率限制,假设系统中存在6W用户,则至多需要开启6W个定时任务来维持每个桶中的令牌数
如上resync函数。该函数会在每次获取令牌之前调用,其实现思路为,若当前时间晚于nextFreeTicketMicros,则计算该段时间内可以生成多少令牌,将生成的令牌加入令牌桶中并更新数据。这样一来,只需要在获取令牌时计算一次即可