java Guava 的 RateLimiter 每分钟而不是秒?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/27711962/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 12:15:48  来源:igfitidea点击:

Guava’s RateLimiter per minutes instead of seconds?

javaguavarate-limiting

提问by Johny19

I'm trying to rate-limit the the number of accounts a user can create with my REST API.

我正在尝试对用户可以使用我的 REST API 创建的帐户数量进行速率限制。

I would have liked to use Guava's RateLimiterto only allow an IP to create, let's say, 5 accounts within 10 minutes, but the RateLimiter.createmethod only takes a doublespecifying the number of permits "per second".

我本来希望使用 GuavaRateLimiter只允许一个 IP 在 10 分钟内创建 5 个帐户,但该RateLimiter.create方法只需要double指定“每秒”许可的数量。

Is there a way to configure RateLimiter to release permits at a granularity greater than one second?

有没有办法配置 RateLimiter 以大于一秒的粒度释放许可?

回答by Jeffrey Bosboom

From the RateLimiter.createjavadoc:

RateLimiter.createjavadoc:

When the incoming request rate exceeds permitsPerSecond the rate limiter will release one permit every (1.0 / permitsPerSecond)seconds.

当传入的请求速率超过 permitPerSecond 时,速率限制器将每秒释放一个许可证(1.0 / permitsPerSecond)

So you can set permitsPerSecondto less than 1.0to release a permit less often than once per second.

因此,您可以设置permitsPerSecond为小于,1.0以低于每秒一次的频率释放许可证。

In your specific case, five accounts in ten minutes simplifies to one account per two minutes, which is one account per 120 seconds. You'd pass 1.0/120for permitsPerSecond.

在您的特定情况下,十分钟内五个帐户简化为每两分钟一个帐户,即每 120 秒一个帐户。你会通过1.0/120permitsPerSecond

In your use case you probably want to accommodate bursty requests for account creations. The RateLimiterspecification doesn't seem to define what happens to unused permits, but the default implementation, SmoothRateLimiter, seems to let permits accrue up to some maximum to satisfy bursts. This class is not public, so there's no javadoc documentation, but the SmoothRateLimitersource has a lengthy comment with a detailed discussion of the current behavior.

在您的用例中,您可能希望适应帐户创建的突发请求。该RateLimiter规范似乎并不确定会发生什么未使用许可证,但默认实施SmoothRateLimiter,似乎让允许累积了一些最大,以满足突发。这个类不是公共的,所以没有 javadoc 文档,但是SmoothRateLimiter源有一个冗长的评论,详细讨论了当前的行为。

回答by Perica Milo?evi?

There's a class called SmoothRateLimiter.SmoothBurstyinside Guava library that implements desired behavior but it has package local access, so we can't use it directly. There's also a Github issue to make access to that class public: https://github.com/google/guava/issues/1974

SmoothRateLimiter.SmoothBurstyGuava 库中有一个类,它实现了所需的行为,但它具有包本地访问权限,因此我们不能直接使用它。还有一个 Github 问题可以公开访问该类:https: //github.com/google/guava/issues/1974

If you're not willing to wait until they release a new version of RateLimiter then you could use reflection to instantiate SmoothBurstyrate limiter. Something like this should work:

如果您不愿意等到他们发布新版本的 RateLimiter,那么您可以使用反射来实例化SmoothBursty速率限制器。这样的事情应该工作:

Class<?> sleepingStopwatchClass = Class.forName("com.google.common.util.concurrent.RateLimiter$SleepingStopwatch");
Method createStopwatchMethod = sleepingStopwatchClass.getDeclaredMethod("createFromSystemTimer");
createStopwatchMethod.setAccessible(true);
Object stopwatch = createStopwatchMethod.invoke(null);

Class<?> burstyRateLimiterClass = Class.forName("com.google.common.util.concurrent.SmoothRateLimiter$SmoothBursty");
Constructor<?> burstyRateLimiterConstructor = burstyRateLimiterClass.getDeclaredConstructors()[0];
burstyRateLimiterConstructor.setAccessible(true);

RateLimiter result = (RateLimiter) burstyRateLimiterConstructor.newInstance(stopwatch, maxBurstSeconds);
result.setRate(permitsPerSecond);
return result;

Yes, new version of Guava might brake your code but if you're willing to accept that risk this might be the way to go.

是的,新版本的 Guava 可能会破坏您的代码,但如果您愿意接受这种风险,这可能是您要走的路。

回答by user4851

I think I came upon the same problem as in the original question, and based on Louis Wasserman's commentthis is what I drew up:

我想我遇到了与原始问题相同的问题,根据 Louis Wasserman 的评论,我提出了以下内容:

import com.google.common.util.concurrent.RateLimiter;
import java.time.Duration;

public class Titrator {

    private final int numDosesPerPeriod;
    private final RateLimiter rateLimiter;
    private long numDosesAvailable;
    private transient final Object doseLock;

    public Titrator(int numDosesPerPeriod, Duration period) {
        this.numDosesPerPeriod = numDosesPerPeriod;
        double numSeconds = period.getSeconds() + period.getNano() / 1000000000d;
        rateLimiter = RateLimiter.create(1 / numSeconds);
        numDosesAvailable = 0L;
        doseLock = new Object();
    }

    /**
     * Consumes a dose from this titrator, blocking until a dose is available.
     */
    public void consume() {
        synchronized (doseLock) {
            if (numDosesAvailable == 0) { // then refill
                rateLimiter.acquire();
                numDosesAvailable += numDosesPerPeriod;
            }
            numDosesAvailable--;
        }
    }

}

The dose meted out by the Titrator is analogous to a permit from a RateLimiter. This implementation assumes that when you consume your first dose, the clock starts ticking on the dosage period. You can consume your max doses per period as fast as you want, but when you reach your max, you have to wait until the period elapses before you can get another dose.

滴定仪提供的剂量类似于来自 RateLimiter 的许可。此实现假设当您服用第一剂时,时钟开始在剂量周期上滴答作响。您可以随心所欲地以最快的速度消耗每个时期的最大剂量,但是当您达到最大剂量时,您必须等到该时期过去才能获得另一剂量。

For a tryConsume()analog to RateLimiter's tryAcquire, you would check that numDosesAvailableis positive.

对于tryConsume()RateLimiter 的模拟tryAcquire,您将检查它numDosesAvailable是否为正。

回答by Dean Hiller

You could also set it to one permit per second and acquire 120 permits for each account.

您还可以将其设置为每秒 1 个许可证,并为每个帐户获取 120 个许可证。

回答by MidTierDeveloper

Just in case your miss it, the RateLimiter does specify what happened to the unused permit. The default behavior is to save the unused link up to one minute RateLimiter.

以防万一您错过了,RateLimiter 会指定未使用的许可证发生了什么。默认行为是将未使用的链接最多保存一分钟RateLimiter

回答by Martin Z

Our workaround for this is to create a RateLimiter class on our own and change the time units. For example, in our case, we want to make a daily rate limit.

我们对此的解决方法是自己创建一个 RateLimiter 类并更改时间单位。例如,在我们的例子中,我们想要设置每日速率限制。

Everything is the same as the RateLimiter except for the acquire(permits) function, where we changed the time unit in (double)TimeUnit.SECONDS.toMicros(1L) to the unit we desire. In our case, we change that into TimeUnit.Day for daily limits.

除了acquire(permits) 函数之外,一切都与RateLimiter 相同,我们将(double)TimeUnit.SECONDS.toMicros(1L) 中的时间单位更改为我们想要的单位。在我们的例子中,我们将其更改为 TimeUnit.Day 以实现每日限制。

Then we create our own smooth RateLimiter and in the doSetRate(double permitsPerDay, long nowMicros) and doGetRate() function we also change the time unit.

然后我们创建我们自己的平滑 RateLimiter 并且在 doSetRate(double permitPerDay, long nowMicros) 和 doGetRate() 函数中,我们还更改了时间单位。