而storedPermitsToWaitTime(double storedPermits, double permitsToTake)这个方法就扮演了这个转换的角色 。它的基础模型是一个连续函数映射存储令牌(从0到maxStoredPermits)在1/Rate的积分 。我们利用空闲时间来存储令牌 , 所以storedPermits本质上是度量了空闲时间(unused time) 。Rate=permits/time , 1/Rate=time/permits , 所以 , 1/Rate和permits相乘就可以算出时间 。即处理一定量的令牌请求时 , 对这个函数进行积分就是对应于后续请求的最小间隔 。
关于storedPermitsToWaitTime的一个例子:如果storedPermits==10 , 我们先需要3个从storedPermits中拿 , storedPermits降到7 , 使用storedPermitsToWaitTime(storedPermits=10, permitsToToken=3)计算节流时间,它将求出函数从7到10的积分值 。
使用积分保证了一个单独的acquire(3)和拆分成{acquire(1),acquire(1),acquire(1)}或{acquire(2),acquire(1)}都是一样的 , 另外 , 不管函数是怎么样的 , 对于函数[7,10]的求积分是等于[7,8],[8,9],[9,10]的总和的 。这就保证了我们可以正确处理不同权重(permits不同)的请求 , 不管函数是什么 , 因此我们可以自由调整函数的算法(很显然 , 只有一个要求:可以被求积分) 。注意 , 如果这个函数画的是个数值刚好是1/QPS的水平线 , 那么这个函数就失去作用了 , 因为它表示存储令牌的速率和生产新令牌的速率是一致的 , 后续中会用到这个技巧 。如果这个函数的值在水平线的下面 , 也就是f(x)<1/Rate , 表示我们减少了积分的面积 , 也就是相同存储的令牌数映射的节流时间相对于正常速率产生的时间变少了 , 也代表RateLimiter在一段时间空闲后变快了 。相反的 , 如果函数的值在水平线的上面 , 表示增加了积分的面积 , 获得存储令牌的消耗要大于新生产令牌 , 那就意味着RateLimiter在一段时间空闲后变慢了 。
最后但也重要 , 如果RateLimiter采用QPS=1的速度 , 那么开销较大的acquire(100)到达时 , 那是没有必要等到100s才开始实际任务 , 为什么不在等待的时候做点什么呢?更好的办法是我们可以马上先开始执行任务(就像它是acquire(1)一个个请求的样子) , 需要把未来的请求推后 。
在这个版本 , 我们允许马上执行任务 , 并把未来的请求推后100s , 所以 , 我们允许在这段时间里完成工作 , 而不是空等 。
这就有了一个很重要的结论:RateLimiter并需要不记住最后请求的时间 , 而只需要记住期望下一个请求到来的时间 。因为我们一直坚持这一点 , 如此我们就可以马上识别出一个请求(tryAcquire(timeout))超时时间是否满足下一个预期请求到达的时间点 。通过这个概念我们可以重新定义“一个空闲的RateLimiter”:当我们观察到“期望下一个请求达到时间”实际已经过去 , 并且差值(now-past)也就是大量时间被转换成storedPermits 。(我们把在空闲时间生产的令牌增加storedPermits) 。所以 , 如果Rate=1 permit/s , 且到达时间恰好比前一次请求晚一秒 , 那么storedPermits将永远不会增加—我们只会在到达时间晚于一秒时增加它 。
SmoothWarmingUp实现原理:顾名思义 , 这里想要实现的是一个有预热能力的RateLimiter , 作者在注释中还画了这幅图:
文章插图
在进入详细实现前 , 让我们先记住几个基本原则:
- Y轴表示RateLimiter(storedPermits数量)的状态 , 不同的storedPermits量对应不同的节流时间 。
- 随着RateLimiter空闲时间的推移 , storedPermits不断向右移动 , 直到maxPermits 。
- 如果在有storedPermits的情况下 , 我们优先使用它 , 所以随着RateLimiter被使用 , 就会向左移动 , 直到0 。
- 当始空闲时 , 我们以恒定的速度前进!我们向右移动的速率被选择为maxPermits/预热周期 。这确保从0到maxpermit所花费的时间等于预热时间 。(由coolDownIntervalMicros方法控制)
- 当使用时 , 正如在前面类注释中解释的那样 , 假设我们想使用K个storedPermits , 它花费的时间等于函数在X许可证和X-K许可证之间的积分 。
推荐阅读
- Java8新特性之空指针异常的克星Optional类
- 跟我一起了解Java到底好在哪?
- 交通事故现场拍照技巧,全是干货!
- 防火墙主备切换案例分析,干货值得收藏
- java服务 tomcat安装,不要太简单
- Java开发必会的Linux命令
- 常用排序算法之JavaScript实现
- Java核心技术-macOS下配置Java11环境
- 分享cmd 窗口中运行 Java 程序小技巧
- 如何理解JAVA类装载器ClassLoader?高级开发才懂的技术点