Guava中限流算法是通过RateLimiter来实现的。关于其设计的理解参见:https://www.cnblogs.com/krock/p/16348037.html
我们先来看下使用效果:
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
Thread task = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ": " + (System.currentTimeMillis() / 1000));
}
});
// 创建一个限流器,参数就是令牌创建的速率 每秒1个
RateLimiter limiter = RateLimiter.create(1);
for (int i = 0; i <20 ; i++) {
//使用的时候,acquire(n) 表示从桶中拿到n个令牌才能继续往下走,如果桶中的令牌不够会阻塞直到桶中生成足够的令牌。
limiter.acquire(1);
executorService.submit(task);
}
}
控制台的内容就是:每秒输出一条信息
下面跟一下create方法和acquire方法
public static RateLimiter create(double permitsPerSecond) {
/*
* The default RateLimiter configuration can save the unused permits of up to one second. This
* is to avoid unnecessary stalls in situations like this: A RateLimiter of 1qps, and 4 threads,
* all calling acquire() at these moments:
*
* T0 at 0 seconds
* T1 at 1.05 seconds
* T2 at 2 seconds
* T3 at 3 seconds
*
* Due to the slight delay of T1, T2 would have to sleep till 2.05 seconds, and T3 would also
* have to sleep till 3.05 seconds.
*/
// 第二个参数创建一个SleepingStopwatch的实例,用来记录这个限流器的开始时间和请求到达的时间
return create(permitsPerSecond, SleepingStopwatch.createFromSystemTimer());
}
看下SmoothBursty是如何构建的:
SmoothBursty(SleepingStopwatch stopwatch, double maxBurstSeconds) {
super(stopwatch); // 给全局的stopwatch赋值
// 上面构建的参数为 1.0,流量突变的最大时间
this.maxBurstSeconds = maxBurstSeconds;
}
setRate, 这个方法是在RateLimiter中的
@Override
final void doSetRate(double permitsPerSecond, long nowMicros) {
// 同步下数据,这里主要是初始化一些数据
resync(nowMicros);
// 计算图形中的stableInterval
double stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;
this.stableIntervalMicros = stableIntervalMicros;
doSetRate(permitsPerSecond, stableIntervalMicros);
}
void resync(long nowMicros) {
// if nextFreeTicket is in the past, resync to now
// 如果 nextFreeTicket 时间落后了,说明RateLimiter有一段时间没用了,需要同步下最新的时间
if (nowMicros > nextFreeTicketMicros) {
// coolDownIntervalMicros 在SmoothBursty中就是两次请求的时间间隔,就是上面计算的stableIntervalMicros也可以认为生成一个令牌的时间间隔
// 这个计算出来在落后的这段时间内又生成了多少个令牌
double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();
// 更新最新的令牌数量 当然不能超过最大的令牌数量
storedPermits = min(maxPermits, storedPermits + newPermits);
nextFreeTicketMicros = nowMicros;
}
}
具体使用的方法acquire
public double acquire(int permits) {
//获取这些令牌permits需要等待的时间
long microsToWait = reserve(permits);
// System.out.println("准备休眠的时间:"+ microsToWait);
//如果等待时间大于0 会进行休眠
stopwatch.sleepMicrosUninterruptibly(microsToWait);
// 返回休眠了多久
return 1.0 * microsToWait / SECONDS.toMicros(1L);
}
final long reserve(int permits) {
// 检查参数必须大于0
checkPermits(permits);
// 加了一把公共的锁
synchronized (mutex()) {
// 第二个参数是 从RateLimiter创建到现在经过多长时间
return reserveAndGetWaitLength(permits, stopwatch.readMicros());
}
}
final long reserveAndGetWaitLength(int permits, long nowMicros) {
// 当令牌不足够的时候,返回下次可以放行的时间
long momentAvailable = reserveEarliestAvailable(permits, nowMicros);
// 计算时间差,就是需要等待的时间,但是必须大于零
return max(momentAvailable - nowMicros, 0);
}
final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
//如果RateLimiter长时间未使用,需要同步令牌桶中的令牌数量和 nextFreeTicketMicros
resync(nowMicros);
long returnValue = nextFreeTicketMicros;
// 比较桶中存储的令牌 storedPermits 和想获取的令牌数量 取最小的那个
// 就是得到本次请求可以消耗的令牌数量
double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
// 需要刷新的令牌数量
// 两种情况: 1: 桶里的令牌数量足够,也就是 storedPermitsToSpend == requiredPermits 那这个 freshPermits == 0
// 2: 桶里的令牌数量不够,也就是 storedPermitsToSpend == storedPermits < requiredPermits
// 那 requiredPermits - storedPermitsToSpend > 0
double freshPermits = requiredPermits - storedPermitsToSpend;
// 计算本次请求获取requiredPermits个令牌需要等待的时间
// + 后面的 (long) (freshPermits * stableIntervalMicros) 就是计算当桶中令牌不够的时候生成新的令牌需要的时间
// + 前面的 storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend) 就是计算从桶里取 storedPermitsToSpend
// 个令牌需要花费的时间 就是前面类注释上提到的函数区间上做积分(SmoothWarmingUp 对于这个限流来说)
// 对于SmoothBursty来说 从桶里取出令牌是不花时间的,直接返回了0,而对于SmoothWarmingUp来说预热就体现在这里,从桶里取出令牌是需要时间的。
long waitMicros =storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
+ (long) (freshPermits * stableIntervalMicros);
//计算 nextFreeTicketMicros,waitMicros 之和,需要等待多久才能得到需要的
this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
//减少桶中的令牌数量
this.storedPermits -= storedPermitsToSpend;
return returnValue;
}
void resync(long nowMicros) {
// if nextFreeTicket is in the past, resync to now
// 如果 nextFreeTicket 时间落后了,说明RateLimiter有一段时间没用了,需要同步下最新的时间
if (nowMicros > nextFreeTicketMicros) {
// coolDownIntervalMicros 在SmoothBursty中就是两次请求的时间间隔,也可以认为生成一个令牌的时间间隔
// 这个计算出来在落后的这段时间内又生成了多少个令牌
double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();
// 更新最新的令牌数量 当然不能超过最大的令牌数量
storedPermits = min(maxPermits, storedPermits + newPermits);
nextFreeTicketMicros = nowMicros;
}
}
在 reserveEarliestAvailable 中可以看到最新的 nextFreeTicketMicros 数据并没有返回出去,所以说这个限流影响的是下次请求,而不是当前请求。
在 waitMicros 这个变量计算的时候分了两部分来的,前面这部分是计算从令牌桶中取出令牌花费的时间,后面部分当桶中令牌数不够的时候需要新生成的时间。
对于前面部分从桶中取出令牌花费的时间,SmoothBursty中的逻辑是:
long storedPermitsToWaitTime(double storedPermits, double permitsToTake) {
return 0L;
}
而 SmoothWarmingUp 中的逻辑是:
long storedPermitsToWaitTime(double storedPermits, double permitsToTake) {
// 可以判断目前桶中的令牌是否超过阈值 thresholdPermits
double availablePermitsAboveThreshold = storedPermits - thresholdPermits;
long micros = 0;
// measuring the integral on the right part of the function (the climbing line)
// 如果超过 thresholdPermits 阈值,则计算梯形的积分来计算时间
if (availablePermitsAboveThreshold > 0.0) {
// 看需要获取的令牌处在梯形部分的令牌有多少
double permitsAboveThresholdToTake = min(availablePermitsAboveThreshold, permitsToTake);
// TODO(cpovirk): Figure out a good name for this variable.
// 就算梯形部分的面积(花费的时间)
// 两次permitsToTime函数计算结果相加就是计算梯形的 上底+下底
double length =
permitsToTime(availablePermitsAboveThreshold)
+ permitsToTime(availablePermitsAboveThreshold - permitsAboveThresholdToTake);
// 梯形的高就是 permitsAboveThresholdToTake micros就是梯形的面积,就是消耗 permitsAboveThresholdToTake 个令牌的时间
micros = (long) (permitsAboveThresholdToTake * length / 2.0);
// 减去梯形部分的令牌,如果 permitsToTake大于0,那么还需要从矩形部分的令牌中获取
permitsToTake -= permitsAboveThresholdToTake;
}
// measuring the integral on the left part of the function (the horizontal line)
// 矩形部分的令牌,速率是固定的就是 stableIntervalMicros,乘上 permitsToTake 就是消耗的时间
micros += (long) (stableIntervalMicros * permitsToTake);
return micros;
}
得到时间之后我们看下: stopwatch.sleepMicrosUninterruptibly(microsToWait); 如何休眠的
public double acquire(int permits) {
//需要等待的时间
long microsToWait = reserve(permits);
// System.out.println("准备休眠的时间:"+ microsToWait);
//如果等待时间大于0 会进行休眠
stopwatch.sleepMicrosUninterruptibly(microsToWait);
return 1.0 * microsToWait / SECONDS.toMicros(1L);
}
protected void sleepMicrosUninterruptibly(long micros) {
if (micros > 0) {
Uninterruptibles.sleepUninterruptibly(micros, MICROSECONDS);
}
}
public static void sleepUninterruptibly(long sleepFor, TimeUnit unit) {
boolean interrupted = false;
try {
long remainingNanos = unit.toNanos(sleepFor);
long end = System.nanoTime() + remainingNanos;
while (true) {
try {
// TimeUnit.sleep() treats negative timeouts just like zero.
NANOSECONDS.sleep(remainingNanos);
return;
} catch (InterruptedException e) {
interrupted = true;
remainingNanos = end - System.nanoTime();
}
}
} finally {
if (interrupted) {
Thread.currentThread().interrupt();
}
}
}
也是调用Thread中的休眠方法。