当超时设置为 >= 1 秒时,番石榴速率限制器不起作用

问题描述

我正在使用 Guava RateLimiter 并且一直在我的代码中创建 ratelimiter,如下所示。

public class RateLimitedCallable<T> implements Callable<T> {

    @Override
    public T call() {
        Boolean permitacquired = RateLimitTest.rateLimiter.tryAcquire(1,1000,TimeUnit.MILLISECONDS);

        if (permitacquired) {
           // do stuff
        } else {
            throw new RuntimeException("Permit was not granted by RateLimiter");
        }
    }
}

public class RateLimitTest {

    public static final RateLimiter rateLimiter = RateLimiter.create(1.0);

    public void test() {
        RateLimitedCallable<String> callable = new RateLimitedCallable<>();
        callable.call();
        callable.call();
        callable.call();
        callable.call();
    }

    public static void main(String[] args) {
        RateLimitTest limiterTest = new RateLimittest();
        limiterTest.test();
    } 

}

RuntimeException 永远不会被抛出。但是,如果我将超时更改为低于 1000 毫秒的值,例如:-

Boolean permitacquired = RateLimitTest.rateLimiter.tryAcquire(1,900,TimeUnit.MILLISECONDS);

我确实看到了 RunTimeException,这意味着 ratelimiter 按预期工作。我不明白为什么当超时时间大于等于 1000 毫秒时 ratelimiter 不强制执行限制。我做错了什么吗?

解决方法

首先,最好记住 tryAcquire 的作用(强调我的):

从此RateLimiter获取给定数量的许可如果可以在不超过指定的timeout的情况下获得,或立即返回false(无需等待)如果在超时到期之前没有授予许可证。

在您的单线程示例中,它从不抛出任何异常是正常的,因为每个调用在获得许可之前大约等待一秒钟。所以这是您的代码中发生的事情:

  1. 第一次调用就知道它可以立即获得许可证。所以它立即获得了许可证。
  2. 在第一个调用完全完成后,第二个调用知道如果它等待就可以获得许可。所以它等待 ~1s 并获得许可。
  3. 在第二次调用完全完成后,第三次调用知道如果它等待,它可以得到许可。所以它等待 ~1s 并获得许可。
  4. 在第三次调用完全完成后,第四次调用知道如果它等待,它可以得到许可。所以它等待~1s并获得许可
  5. 节目结束。

现在尝试在多线程示例中使用它,您将开始看到几次失败和几次成功。因为他们都想同时拿到许可证。

  1. 第一个获得的是快乐。
  2. 然后第二个知道它是否等待了大约 1 秒,它可以得到它,所以它一直等到它得到它。
  3. 第三个和第四个看到队列中已经有 2 个呼叫,并且知道他们必须等待 2 秒钟才能获得许可。所以他们放弃了,因为 2 秒大于您设置的 1 秒超时。

所以基本上,只需使用多线程环境来测试它是否会发生。

  @Test
  void test() {
    var rateLimiter = RateLimiter.create(1.0);
    var stopwatch = Stopwatch.createStarted();
    var executor = Executors.newFixedThreadPool(4);
    for (var i = 0; i < 4; i++) {
      executor.submit(() -> {
        if (rateLimiter.tryAcquire(1,1000,TimeUnit.MILLISECONDS)) {
          System.out.printf("Acquired after %s%n",stopwatch);
        } else {
          System.out.printf("Gave up trying to acquire after %s%n",stopwatch);
        }
      });
    }
    executor.shutdown();
    try {
      if (!executor.awaitTermination(5000,TimeUnit.MILLISECONDS)) {
        executor.shutdownNow();
      }
    } catch (InterruptedException e) {
      executor.shutdownNow();
    }
  }

结果是

Acquired after 12.76 ms
Gave up trying to acquire after 12.41 ms
Gave up trying to acquire after 12.43 ms
Acquired after 1.004 s