为什么 Guava RateLimiter 不再限制每秒太大的许可数?

Why doesn't Guava RateLimiter limit for too large permits per second anymore?

我正在使用一些 HTTP 库将一些数据上传到一些网络服务,并且需要限制每秒上传的数据量。使用的限制器是 Guava RateLimiter, pretty much following the second example limiting some data stream. Two differences for me: I'm providing some InputStream to the consumer and am using aquire in the read 方法,一次只提供一个字节。没有不同大小的数据包或类似的数据包,根据我的理解,这应该会让事情变得更容易。此外,我的每秒许可数大于示例。

对于低数字,事情似乎运作良好,但对于更高数字则不然,而且恰好有一个数字开始不再起作用。我在 KiB/s 方面进行了限制,并且包括 1 * 1024 * 976 在内的所有内容都有效,但是对于 977 事情开始失败并且限制似乎不再适用。使用一些网络监视器可以很容易地看到这一点,第一个配置限制上传 ~7-8 MBit/s,而后者提高到 60 或更多,这取决于传出接口的使用方式等。根据我的理解,上传量的增加应该小得多,只有 1 KiB/s,因此根本不显着。

我可以使用以下代码重现问题:

import com.google.common.util.concurrent.RateLimiter;

public class Test
{
    public static void main(String[] args)
    {
        RateLimiter rateLimiter = RateLimiter.create(1 * 1024 * 976);
        RateLimiter msgLimiter  = RateLimiter.create(1);
        long        aquired     = 0L;

        while (true)
        {
            rateLimiter.acquire();
            ++aquired;

            if (msgLimiter.tryAcquire())
            {
                System.out.println(
                    String.format(  "Aquired: %d MBit/s",
                                    (aquired * 8) / (1024 * 1024)));
                aquired = 0;
            }
        }
    }
}

976 的结果:

Aquired: 0 MBit/s
Aquired: 7 MBit/s
Aquired: 7 MBit/s
Aquired: 7 MBit/s
Aquired: 7 MBit/s

977 的结果:

Aquired: 0 MBit/s
Aquired: 77 MBit/s
Aquired: 85 MBit/s
Aquired: 82 MBit/s
Aquired: 83 MBit/s

你知道为什么会这样吗?谢谢!

我已经阅读了有关 bursts 等的 RateLimiter 的 "downsides",但不知道这是否以及如何解释我的问题。

使用 TimedSemaphore 不会出现问题:

import java.util.concurrent.TimeUnit;

import org.apache.commons.lang3.concurrent.TimedSemaphore;

import com.google.common.util.concurrent.RateLimiter;

public class Test
{
    public static void main(String[] args) throws InterruptedException
    {
        int             rateLimit   = 1 * 1024 * 2000;
        //RateLimiter   rateLimiter = RateLimiter.create(rateLimit);
        TimedSemaphore  rateLimiter = new TimedSemaphore(1, TimeUnit.SECONDS, rateLimit);
        RateLimiter     msgLimiter  = RateLimiter.create(1);
        long            aquired     = 0L;

        while (true)
        {
            rateLimiter.acquire();
            ++aquired;

            if (msgLimiter.tryAcquire())
            {
                System.out.println(
                    String.format(  "Aquired: %d MBit/s",
                                    (aquired * 8) / (1024 * 1024)));
                aquired = 0;
            }
        }
    }
}

976 的结果与以前相同,因此值越大越有趣:

Aquired: 0 MBit/s
Aquired: 15 MBit/s
Aquired: 15 MBit/s
Aquired: 15 MBit/s
Aquired: 15 MBit/s

Guava RateLimiter 似乎只能达到每秒 1,000,000 次请求调用的限制。如果您尝试每秒执行 1,000,001 次 aquire 调用,则对 aquire 的调用根本不会等待(aquire() 的 return 值始终为 0.0)-> 不会发生限制。

因此,以下情况按预期工作:

RateLimiter.create(1000000l); // 这将在使用 aquire() / aquire(1)

时起作用

RateLimiter.create(100000000l); // 这将在使用 aquire(100) 或更高版本时起作用。

假设典型的网络流量永远不会逐字节接收,而是以 100-1000 字节的块为单位,Guava RateLimiter 的工作上限为 100,000,000(~762 MBit)到 1,000,000,000(7.45 GBit/s) 字节每秒。

如果这些值对您的项目来说足够了,您可以坚持使用 Guava RateLimiter。如果不是,我建议改用 Apache TimedSemaphore。