Java干货丨限流常规设计和实例( 三 )


也可以将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 , 新的许可证(对于任何剩余的许可) 。
下面用一个例子来解释工作原理:假设RateLimiter每秒生成一个token , 每一秒过去而RateLimiter没有被使用的话 , 就会把storedPermits加1 。我们不使用RateLimiter10秒(即 , 我们期望在一个X时间有一个请求 , 可是实际在X+10秒后请求才到达;这个和最后一个段落的结论有关) , storedPermits会变成10(假定maxStoredPermits>=10) 。在那时一个调用acquire(3)的请求到达 。我们使用storedPermits将它降到7来响应这个请求(how this is translated to throttling time is discussed later) , 之后acquire(10)的请求马上到达 。我们使用storedPermits中全部剩余的7个permits , 剩下的3个使用RateLimiter生产的新permits 。我们已经知道 , 获得三个fresh permits需要多少时间:如果速率是“1秒一个token” , 那么需要3秒时间 。但是提供7个存储的permits意味着什么?前面的解释中没有唯一的答案 。如果主要关注处理利用不足的情况 , 我们存储permits为了给出比fresh permits快 , 因为利用不足=空闲资源 。如果主要关注overflow的场景 , 存储permits可以比fresh permits慢给出 。所以 , 我们需要一个方法将storedPermits转换成节流时间(throttling time) 。


推荐阅读