当超时设置为 >= 1 秒时,Guava ratelimiter 不起作用

Guava ratelimiter doesn't work when timeout set to >= 1 second

我正在使用 Guava 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,这意味着速率限制器按预期工作。我不明白为什么当超时时间大于等于 1000 毫秒时,速率限制器不强制执行限制。我做错了什么吗?

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

Acquires the given number of permits from this RateLimiter if it can be obtained without exceeding the specified timeout, or returns false immediately (without waiting) if the permits would not have been granted before the timeout expired.

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

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

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

  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