Guava(二)限流算法的使用
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()); }
static RateLimiter create(double permitsPerSecond, SleepingStopwatch stopwatch) {
// 上面我们使用的限流器实例是SmoothBursty RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */); rateLimiter.setRate(permitsPerSecond); return rateLimiter; }
看下SmoothBursty是如何构建的:
SmoothBursty(SleepingStopwatch stopwatch, double maxBurstSeconds) { super(stopwatch); // 给全局的stopwatch赋值
// 上面构建的参数为 1.0,流量突变的最大时间 this.maxBurstSeconds = maxBurstSeconds; }
setRate, 这个方法是在RateLimiter中的
public final void setRate(double permitsPerSecond) { checkArgument( permitsPerSecond > 0.0 && !Double.isNaN(permitsPerSecond), "rate must be positive"); synchronized (mutex()) { // 加了一把互斥锁
//这个方法需要子类重写的,SmoothBursty和SmoothWarmingUp 逻辑不一样 doSetRate(permitsPerSecond, stopwatch.readMicros()); } }
@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中的休眠方法。