也可以将RateLimiter配置为有一个预热期 , 在此期间 , 每秒钟发出的许可稳步增加 , 直到达到稳定的速率 。(SmoothWarmingUp)
一个例子:我们一个任务列表需要执行 , 但是每秒提交的任务个数不能超过2个 。
final RateLimiter rateLimiter = RateLimiter.create(2.0); // rate is "2 permits per second" void submitTasks(List<Runnable> tasks, Executor executor) { for (Runnable task : tasks) { rateLimiter.acquire(); // may wait executor.execute(task); } }另一个例子:我们生产一个数据流想以5kb每秒的速度传输 , 这个可以通过将一个permit对应一个byte , 并定义5000/s的速率 。
final RateLimiter rateLimiter = RateLimiter.create(5000.0); // rate = 5000 permits per secondvoid submitPacket(byte[] packet) { rateLimiter.acquire(packet.length); networkService.send(packet);}有一点需要注意 , 请求令牌的数量不会影响请求自身的节流 , 但是会影响下一次请求 , 如果一个需要大量令牌的任务到达时 , 将马上授予 , 但是下一次请求将会为上次昂贵任务付出代价 。
Guava RateLimiter是如何设计的 , 为什么?
对于RateLimiter来说最关键的特性是:在一般情况下允许的最大速率-“恒定速率” 。这需要对传入的请求强制执行即计算“节流” , 也需要在适当的节流时间,让请求线程等待 。
维护QPS的速率最简单的方式就是保留最后准许请求的时间戳 , 确保能计算从那是到现在流逝的时间和QPS 。举个例子 , 一个QPS为5(5个token每秒)的速率 , 如果我们确保请求被准许时比最后的请求早200ms , 我们就达到了想要的速率 。如果最后准许的请求只过去100ms , 那么我们需要等待100ms 。在这个速率下 , 提供新的15个许可证(即调用acquire(15))自然需要3s 。
RateLimiter只记住最后一次请求的时间戳 , 只是对过去非常浅的记忆 , 意识到这点很重要 。假如很长一段时间没有使用RateLimiter , 然后来了一个请求 , 是马上准许吗?这样的RateLimiter将马上忘记刚刚过去的利用不足的时间 。最终 , 因为现实请求速率的不规则导致要么利用不足或溢出的结果 。过去利用不足意味着存在过剩可用资源 , 所以RateLimiter想要利用这些资源 , 就需要提速一会儿 。在计算机网络中应用的速率(带宽限制) , 那些过去利用不足的一般会转换成“几乎为空的缓冲区” , 可以马上用于后续流量 。
另一方面 , 过去利用不足也意味着“服务器对未来请求的处理的准备变得越来越少” , 也就是说 , 缓存变过期 , 请求将更有可能触发昂贵的操作(一个更极端的例子 , 当服务启动后 , 它通常忙于提高自己的速度) 。
为了处理这类场景 , 我们增加了一个额外的维度 , 将过去利用不足建模到storedPermits字段 。没有利用不足情况时这个字段的值是0 , 随着逐渐到达充分利用不足 , 这个字段的值会增大到一个最大值maxStoredPermits 。
所以 , 当执行acquire(permits)方法来请求许可证从两方面获取:
- 1 , 存储的许可证(如果有可用的) ,
- 2 , 新的许可证(对于任何剩余的许可) 。
推荐阅读
- Java8新特性之空指针异常的克星Optional类
- 跟我一起了解Java到底好在哪?
- 交通事故现场拍照技巧,全是干货!
- 防火墙主备切换案例分析,干货值得收藏
- java服务 tomcat安装,不要太简单
- Java开发必会的Linux命令
- 常用排序算法之JavaScript实现
- Java核心技术-macOS下配置Java11环境
- 分享cmd 窗口中运行 Java 程序小技巧
- 如何理解JAVA类装载器ClassLoader?高级开发才懂的技术点