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

我试图限制用户可以使用我的REST API创建的帐户数量。

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

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

RateLimiter.create javadoc:

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

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

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

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

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

如果您不愿意等到他们发布新版本的RateLimiter,那么您可以使用reflection来实例化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可能会破坏您的代码,但如果您愿意接受这种风险,这可能就是您的选择。

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

我想我遇到了与原问题相同的问题,根据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--; } } } 

滴定仪测量的剂量类似于RateLimiter的许可证。 该实施假设当您消耗第一剂时,时钟开始在剂量周期上滴答作响。 您可以按照您想要的速度消耗每个时期的最大剂量,但是当您达到最大值时,您必须等到该时间段过去才能再次服用。

对于tryConsume() RateLimiter的tryAcquiretryConsume() ,您将检查numDosesAvailable是否为正数。

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