平滑限流
什么是平滑限流? 平就是平稳、滑是没有折线(好像也不太准确),没有曲线?(曲线其实也可以有Smooth的意思)丝滑?总之是比较Smooth就对了。guava中RateLimiter 的实现只有平滑限流的实现,即SmoothRateLimiter。 而SmoothRateLimiter 也是抽象的,它有两个实现,一个是突发实现即SmoothBursty,一个是预热实现即SmoothWarmingUp。
突发限流的方法是一个参数,而预热限流的方法是三个参数的方法,具体可见源码:
static RateLimiter create( SleepingStopwatch stopwatch, double permitsPerSecond, long warmupPeriod, TimeUnit unit) { RateLimiter rateLimiter = new SmoothWarmingUp(stopwatch, warmupPeriod, unit); rateLimiter.setRate(permitsPerSecond); return rateLimiter; }
可见预热限流是SmoothWarmingUp
预热时间为0 的预热限流
预热时间为0 的预热限流 是一个什么情况?
注意到上方法的第一个参数是和突发限流的方法是一样的,都是每秒的许可数。上方法的第二个参数是预热时间。最后一个参数是时间单位。 第一个参数自然是不能小于等于0的, 不能失去了意义。 第二个参数当然不能小于0,否则无法解释;那它是否可以等于0? 测试发现是可以的。
当预热限流的方法的第二个参数是0的时候,预热的效果就消失,也就是没有了预热。这就变成了一个特殊的情况, 也就是也非突发、既非预热。SmoothWarmingUp的图形变成了一个点! 尽管如此,虽然此时预热时间为0,那么它是否就没有了任何的缓存许可的功能? 非也!
通过上文的分析,加上测试,发现SmoothWarmingUp 并不会积累permits。 就是说,使用的时候立即达到最高速度,不使用的时候立即冷却,此时速度呢? 其实冷却的时候速度都也不重要,实际上是无穷大,但是没有意义,因为加速的过程为0, 所以也可以把冷却的速度理解为0(更好理解)。就是说,基本上SmoothWarmingUp 只会使用2个速度:使用时max,不使用时0;
RateLimiter r = RateLimiter.create(2, 0, TimeUnit.MILLISECONDS);
漏桶,因为它本质上是令牌桶上做了修改。SmoothWarmingUp
SmoothWarmingUp 到底是漏桶还是令牌桶
其实上文说错了。答案是明确的,SmoothWarmingUp 应该算是漏桶,一般情况下,也就是当预热时间不为0的时候。我们知道SmoothWarmingUp有一个冷却的过程,而这个冷却的过程,就是可以理解为漏桶的令牌漏出的过程!但是呢, 正常理解的漏桶,如果桶内令牌足够,应该是可以直接获取而不用等待的(不管是否已经开始了冷却)。而我们知道SmoothWarmingUp不是的,SmoothWarmingUp一旦开始了冷却,它就必须要至少需要等待一些些的预热时间,也就是说需要比稳定状态时更久的时间!!
而且,我们知道,SmoothWarmingUp的整个加热过程(包括预热和准稳定过程) 并不是一个直线,而是折线,这和我们想象中的理想的漏桶恐怕还是不一样。
测试观察
@org.junit.Test public void testSmoothWarmingWith0() { RateLimiter r = RateLimiter.create(2, 0, TimeUnit.MILLISECONDS); while (true) { System.out.println("get 2 tokens: " + r.acquire(2) + "s");// 2个需要1s,但许可获取时间由下一次获取承担; 循环的首次,此行等待时间为0,第二次以后的等待时间为⑤ 行的许可获取时间,即0.5s try { Thread.sleep(1500);// 休息1.5s,可以完全消耗上行的2个许可,并剩出来0.5s,但是因为 预热/冷却时间为0,所以这个0.5s 其实是完全白白的流逝了.. } catch (Exception e) { } System.out.println("get 3 tokens: " + r.acquire(3) + "s");// 因为 预热/冷却时间为0,前面的许可已经完全被补偿,所以此处3个需要1.5s,但时间地点时间为0,许可获取时间由下一次获取承担 System.out.println("get 1 tokens: " + r.acquire(1) + "s");// 上一个方法的实际等待时间为0,因为它由此方法承担—— 此方法实际等待时间为上一个方法的3个许可获取时间,即1.5s System.out.println("get 1 tokens: " + r.acquire(1) + "s");// 此方法实际等待时间为上一个方法的1个许可获取时间,即0.5s System.out.println("get 1 tokens: " + r.acquire(1) + "s");// ⑤ 此方法实际等待时间为上一个方法的1个许可获取时间,即0.5s System.out.println("end"); } }
观察日志打印,发现符合预期。
get 2 tokens: 0.0s get 3 tokens: 0.0s get 1 tokens: 1.499681s get 1 tokens: 0.494564s get 1 tokens: 0.498945s end get 2 tokens: 0.499013s get 3 tokens: 0.0s get 1 tokens: 1.499839s get 1 tokens: 0.499698s get 1 tokens: 0.499081s end get 2 tokens: 0.499659s get 3 tokens: 0.0s get 1 tokens: 1.499842s get 1 tokens: 0.498895s get 1 tokens: 0.499479s
SmoothWarmingUp 整个加热过程是不是可以为一条直线?
理论上来讲,SmoothWarmingUp 的斜率变成0,那么整个加热过程的就是一天直线。但是其实这个在SmoothWarmingUp 中是不可能发生的。 因为SmoothWarmingUp 的一个假设是 预热时间 = 冷却时间。如果斜率变成了0,那么预热时间 = 冷却时间 就不可能满足,除非预热时间变成了0,这就又回到了我们开始讨论的情况,也就是变成了一个点。