Java 在 N 秒内限制对 M 个请求的方法调用
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1407113/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Throttling method calls to M requests in N seconds
提问by vtrubnikov
I need a component/class that throttles execution of some method to maximum M calls in N seconds (or ms or nanos, does not matter).
我需要一个组件/类来限制某些方法的执行以在 N 秒(或 ms 或 nanos,无关紧要)内最多调用 M 次。
In other words I need to make sure that my method is executed no more than M times in a sliding window of N seconds.
换句话说,我需要确保我的方法在 N 秒的滑动窗口中执行不超过 M 次。
If you don't know existing class feel free to post your solutions/ideas how you would implement this.
如果您不知道现有课程,请随时发布您的解决方案/想法,您将如何实现这一点。
采纳答案by Michael Borgwardt
I'd use a ring bufferof timestamps with a fixed size of M. Each time the method is called, you check the oldest entry, and if it's less than N seconds in the past, you execute and add another entry, otherwise you sleep for the time difference.
我会使用一个固定大小为 M 的时间戳环形缓冲区。每次调用该方法时,您都会检查最旧的条目,如果过去不到 N 秒,则执行并添加另一个条目,否则您就睡觉因为时差。
回答by Kevin
Read up on the Token bucketalgorithm. Basically, you have a bucket with tokens in it. Every time you execute the method, you take a token. If there are no more tokens, you block until you get one. Meanwhile, there is some external actor that replenishes the tokens at a fixed interval.
阅读令牌桶算法。基本上,您有一个带有令牌的存储桶。每次执行该方法时,都会获取一个令牌。如果没有更多的令牌,你就会阻塞直到你得到一个。同时,有一些外部参与者以固定的时间间隔补充代币。
I'm not aware of a library to do this (or anything similar). You could write this logic into your code or use AspectJ to add the behavior.
我不知道有一个图书馆可以做到这一点(或任何类似的东西)。您可以将此逻辑写入代码或使用 AspectJ 添加行为。
回答by erickson
In concrete terms, you should be able to implement this with a DelayQueue
. Initialize the queue with M
Delayed
instances with their delay initially set to zero. As requests to the method come in, take
a token, which causes the method to block until the throttling requirement has been met. When a token has been taken, add
a new token to the queue with a delay of N
.
具体来说,您应该能够使用DelayQueue
. 使用M
Delayed
延迟初始设置为零的实例初始化队列。随着对方法的请求传入,take
一个令牌会导致方法阻塞,直到满足节流要求。当一个令牌被拿走时,add
一个新的令牌到队列中,延迟为N
。
回答by Eugene Yokota
Although it's not what you asked, ThreadPoolExecutor
, which is designed to cap to M simultaneous requests instead of M requests in N seconds, could also be useful.
尽管这不是您所要求的,ThreadPoolExecutor
但它旨在限制 M 个同时请求而不是 N 秒内的 M 个请求,也可能很有用。
回答by Krishas
I have implemented a simple throttling algorithm.Try this link, http://krishnaprasadas.blogspot.in/2012/05/throttling-algorithm.html
我已经实现了一个简单的节流算法。试试这个链接, http://krishnaprasadas.blogspot.in/2012/05/throttling-algorithm.html
A brief about the Algorithm,
算法简介,
This algorithm utilizes the capability of Java Delayed Queue. Create a delayedobject with the expected delay (here 1000/M for millisecond TimeUnit). Put the same object into the delayed queue which will intern provides the moving window for us. Then before each method call takethe object form the queue, take is a blocking call which will return only after the specified delay, and after the method call don't forget to put the object into the queue with updated time(here current milliseconds).
该算法利用了 Java Delayed Queue的能力。创建具有预期延迟的延迟对象(此处为 1000/M 表示毫秒TimeUnit)。将相同的对象放入延迟队列,它将为我们提供移动窗口。然后,每个方法调用之前采取了对象形成队列,采取的是阻塞调用,它只会在指定延迟后返回,并在方法调用后,不要忘了把对象放入队列更新时间(这里当前毫秒) .
Here we can also have multiple delayed objects with different delay. This approach will also provide high throughput.
这里我们也可以有多个不同延迟的延迟对象。这种方法还将提供高吞吐量。
回答by Duarte Meneses
This depends in the application.
这取决于应用程序。
Imagine the case in which multiple threadswant a token to do some globally rate-limited actionwith no burst allowed(i.e. you want to limit 10 actions per 10 seconds but you don't want 10 actions to happen in the first second and then remain 9 seconds stopped).
想象一下这样的情况,其中多个线程希望令牌执行一些全局速率限制的操作,不允许突发(即,您希望每 10 秒限制 10 个操作,但您不希望在第一秒内发生 10 个操作,然后保持不变) 9 秒停止)。
The DelayedQueue has a disadvantage: the order at which threads request tokens might not be the order at which they get their request fulfilled. If multiple threads are blocked waiting for a token, it is not clear which one will take the next available token. You could even have threads waiting forever, in my point of view.
DelayedQueue 有一个缺点:线程请求令牌的顺序可能不是它们完成请求的顺序。如果多个线程被阻塞等待令牌,则不清楚哪个线程将获取下一个可用令牌。在我看来,你甚至可以让线程永远等待。
One solution is to have a minimum interval of time between two consecutive actions, and take actions in the same order as they were requested.
一种解决方案是在两个连续操作之间设置最小时间间隔,并按照请求的顺序执行操作。
Here is an implementation:
这是一个实现:
public class LeakyBucket {
protected float maxRate;
protected long minTime;
//holds time of last action (past or future!)
protected long lastSchedAction = System.currentTimeMillis();
public LeakyBucket(float maxRate) throws Exception {
if(maxRate <= 0.0f) {
throw new Exception("Invalid rate");
}
this.maxRate = maxRate;
this.minTime = (long)(1000.0f / maxRate);
}
public void consume() throws InterruptedException {
long curTime = System.currentTimeMillis();
long timeLeft;
//calculate when can we do the action
synchronized(this) {
timeLeft = lastSchedAction + minTime - curTime;
if(timeLeft > 0) {
lastSchedAction += minTime;
}
else {
lastSchedAction = curTime;
}
}
//If needed, wait for our time
if(timeLeft <= 0) {
return;
}
else {
Thread.sleep(timeLeft);
}
}
}
回答by schnatterer
What worked out of the box for me was Google Guava RateLimiter.
对我来说开箱即用的是 Google Guava RateLimiter。
// Allow one request per second
private RateLimiter throttle = RateLimiter.create(1.0);
private void someMethod() {
throttle.acquire();
// Do something
}
回答by SergeZ
Try to use this simple approach:
尝试使用这种简单的方法:
public class SimpleThrottler {
private static final int T = 1; // min
private static final int N = 345;
private Lock lock = new ReentrantLock();
private Condition newFrame = lock.newCondition();
private volatile boolean currentFrame = true;
public SimpleThrottler() {
handleForGate();
}
/**
* Payload
*/
private void job() {
try {
Thread.sleep(Math.abs(ThreadLocalRandom.current().nextLong(12, 98)));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.err.print(" J. ");
}
public void doJob() throws InterruptedException {
lock.lock();
try {
while (true) {
int count = 0;
while (count < N && currentFrame) {
job();
count++;
}
newFrame.await();
currentFrame = true;
}
} finally {
lock.unlock();
}
}
public void handleForGate() {
Thread handler = new Thread(() -> {
while (true) {
try {
Thread.sleep(1 * 900);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
currentFrame = false;
lock.lock();
try {
newFrame.signal();
} finally {
lock.unlock();
}
}
}
});
handler.start();
}
}
}
回答by gtonic
Apache Camelalso supports comes with Throttlermechanism as follows:
Apache Camel还支持自带的Throttler机制如下:
from("seda:a").throttle(100).asyncDelayed().to("seda:b");
回答by peterreilly
This is an update to the LeakyBucket code above. This works for a more that 1000 requests per sec.
这是对上述 LeakyBucket 代码的更新。这适用于每秒 1000 个以上的请求。
import lombok.SneakyThrows;
import java.util.concurrent.TimeUnit;
class LeakyBucket {
private long minTimeNano; // sec / billion
private long sched = System.nanoTime();
/**
* Create a rate limiter using the leakybucket alg.
* @param perSec the number of requests per second
*/
public LeakyBucket(double perSec) {
if (perSec <= 0.0) {
throw new RuntimeException("Invalid rate " + perSec);
}
this.minTimeNano = (long) (1_000_000_000.0 / perSec);
}
@SneakyThrows public void consume() {
long curr = System.nanoTime();
long timeLeft;
synchronized (this) {
timeLeft = sched - curr + minTimeNano;
sched += minTimeNano;
}
if (timeLeft <= minTimeNano) {
return;
}
TimeUnit.NANOSECONDS.sleep(timeLeft);
}
}
and the unittest for above:
以及上面的单元测试:
import com.google.common.base.Stopwatch;
import org.junit.Ignore;
import org.junit.Test;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
public class LeakyBucketTest {
@Test @Ignore public void t() {
double numberPerSec = 10000;
LeakyBucket b = new LeakyBucket(numberPerSec);
Stopwatch w = Stopwatch.createStarted();
IntStream.range(0, (int) (numberPerSec * 5)).parallel().forEach(
x -> b.consume());
System.out.printf("%,d ms%n", w.elapsed(TimeUnit.MILLISECONDS));
}
}