我正在尝试对用户可以使用REST API创建的帐户数量进行限速。
我本来希望使用Guava的RateLimiter
只允许IP在10分钟内创建5个帐户,但RateLimiter.create
方法只需要double
指定许可证数量“每秒”。
有没有办法配置RateLimiter以大于一秒的粒度释放许可?
答案 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()函数中,还更改了时间单位。