番石榴的每分钟而不是秒RateLimiter?

问题描述 投票:9回答:6

我试图速率限制,用户可以用我的REST API创建的帐户数。

我本来希望利用番石榴的RateLimiter只允许一个IP在10分钟内创造,让我们说,5个帐户,但RateLimiter.create方法只需要double指定许可证的数目“每秒”。

是否存在一种配置RateLimiter在粒度超过一秒更大的释放证的方法吗?

java guava rate-limiting
6个回答
14
投票

RateLimiter.create的javadoc:

当输入请求率超过permitsPerSecond速率限制器将释放每(1.0 / permitsPerSecond)秒一个许可证。

所以,你可以设置permitsPerSecond不到1.0释放许可证往往小于每秒一次。

在您的具体情况,在十分钟内五个账户简化为每两分钟是帐户,这是了每120个秒钟就有一个帐户。你会通过1.0/120permitsPerSecond

在您的使用情况下,你可能要适应帐户建立突发请求。该RateLimiter规范似乎并不确定会发生什么不用的许可证,但是默认的实现,SmoothRateLimiter,似乎让允许累积了一些最大,以满足突发。这个类是不公开,所以没有Javadoc文档,但SmoothRateLimiter源与当前的行为的详细讨论一个漫长的评论。


5
投票

有番石榴库中的一类叫做SmoothRateLimiter.SmoothBursty实现期望的行为,但它有包装的本地访问,所以我们不能直接使用它。还有一个问题Github上,使访问该类市民:https://github.com/google/guava/issues/1974

如果你才会发布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;

是的,番石榴的新版本可能会破坏你的代码,但如果你愿意接受的风险,这可能是要走的路。


3
投票

你也可以将它设置为每秒一个许可证,并获得120只允许为每个帐户。


2
投票

我想,我来到了同样的问题在原来的问题,并以路易·沃瑟曼的comment这是我画了起来:

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--;
        }
    }

}

通过滴定量出的剂量类似于从RateLimiter许可证。此实现假定,当你消耗你的第一个剂量,时钟开始上量期滴答作响。您可以以最快的速度,你要消耗每一段你的最大剂量,但是,当你达到最大值,你必须等待,直到周期结束之前,你可以得到另一剂量。

对于tryConsume()模拟RateLimiter的tryAcquire,你会检查numDosesAvailable是积极的。


0
投票

万一你的思念吧,RateLimiter并指定了什么事未使用的许可证。默认行为是保存未使用的链路长达一个分钟RateLimiter


0
投票

我们对这个解决办法是建立在我们自己的一个RateLimiter类和更改时间单位。例如,在我们的例子中,我们要每天速率限制。

一切都是一样的RateLimiter除了获取(许可证)的功能,我们在(双)TimeUnit.SECONDS.toMicros(1L)改变的时间单位,以我们想要的单元。在我们的例子中,我们改变成TimeUnit.Day每日限额。

然后,我们创造我们自己的平稳RateLimiter并在doSetRate(双permitsPerDay,长nowMicros)和doGetRate()函数中,我们也改变了时间单位。

© www.soinside.com 2019 - 2024. All rights reserved.