我试图速率限制,用户可以用我的REST API创建的帐户数。
我本来希望利用番石榴的RateLimiter
只允许一个IP在10分钟内创造,让我们说,5个帐户,但RateLimiter.create
方法只需要double
指定许可证的数目“每秒”。
是否存在一种配置RateLimiter在粒度超过一秒更大的释放证的方法吗?
从RateLimiter.create
的javadoc:
当输入请求率超过permitsPerSecond速率限制器将释放每
(1.0 / permitsPerSecond)
秒一个许可证。
所以,你可以设置permitsPerSecond
不到1.0
释放许可证往往小于每秒一次。
在您的具体情况,在十分钟内五个账户简化为每两分钟是帐户,这是了每120个秒钟就有一个帐户。你会通过1.0/120
为permitsPerSecond
。
在您的使用情况下,你可能要适应帐户建立突发请求。该RateLimiter
规范似乎并不确定会发生什么不用的许可证,但是默认的实现,SmoothRateLimiter
,似乎让允许累积了一些最大,以满足突发。这个类是不公开,所以没有Javadoc文档,但SmoothRateLimiter
源与当前的行为的详细讨论一个漫长的评论。
有番石榴库中的一类叫做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;
是的,番石榴的新版本可能会破坏你的代码,但如果你愿意接受的风险,这可能是要走的路。
你也可以将它设置为每秒一个许可证,并获得120只允许为每个帐户。
我想,我来到了同样的问题在原来的问题,并以路易·沃瑟曼的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
是积极的。
万一你的思念吧,RateLimiter并指定了什么事未使用的许可证。默认行为是保存未使用的链路长达一个分钟RateLimiter。
我们对这个解决办法是建立在我们自己的一个RateLimiter类和更改时间单位。例如,在我们的例子中,我们要每天速率限制。
一切都是一样的RateLimiter除了获取(许可证)的功能,我们在(双)TimeUnit.SECONDS.toMicros(1L)改变的时间单位,以我们想要的单元。在我们的例子中,我们改变成TimeUnit.Day每日限额。
然后,我们创造我们自己的平稳RateLimiter并在doSetRate(双permitsPerDay,长nowMicros)和doGetRate()函数中,我们也改变了时间单位。