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

时间:2014-12-30 20:18:29

标签: java guava rate-limiting

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

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

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

6 个答案:

答案 0 :(得分:14)

来自RateLimiter.create javadoc:

  

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

因此,您可以将permitsPerSecond设置为小于1.0,以便每秒发布一次许可,而不是每秒一次。

在您的具体情况下,10分钟内的五个帐户每两分钟简化为一个帐户,即每120秒一个帐户。您将1.0/120传递给permitsPerSecond

在您的用例中,您可能希望适应帐户创建的突发请求。 RateLimiter规范似乎没有定义未使用的许可证会发生什么,但默认实现SmoothRateLimiter似乎允许许可证达到某个最大值以满足突发。这个类不公开,因此没有javadoc文档,但SmoothRateLimiter源有一个冗长的注释,详细讨论了当前的行为。

答案 1 :(得分:5)

在Guava库中有一个名为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;

是的,新版本的Guava可能会破坏您的代码,但如果您愿意接受这种风险,那么可能就是这样。

答案 2 :(得分:3)

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

答案 3 :(得分:1)

我想我遇到的问题与原问题相同,基于Louis Wasserman的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是否为正。

答案 4 :(得分:0)

万一你想念它,RateLimiter会指明未使用的许可证发生了什么。默认行为是将未使用的链接保存一分钟RateLimiter

答案 5 :(得分:0)

为此,我们的解决方法是自行创建RateLimiter类并更改时间单位。例如,在我们的示例中,我们要设置每日汇率限制。

除acquire(permits)函数外,所有其他事情都与RateLimiter相同,在该函数中,我们将(double)TimeUnit.SECONDS.toMicros(1L)中的时间单位更改为所需的单位。在本例中,我们将其更改为TimeUnit.Day以设置每日限制。

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