一部のメソッドの実行を N 秒 (または ms または nanos は関係ありません) で最大 M 呼び出しに調整するコンポーネント/クラスが必要です。
つまり、N 秒のスライディング ウィンドウでメソッドが M 回しか実行されないようにする必要があります。
既存のクラスがわからない場合は、これをどのように実装するかについて、ソリューション/アイデアを自由に投稿してください。
一部のメソッドの実行を N 秒 (または ms または nanos は関係ありません) で最大 M 呼び出しに調整するコンポーネント/クラスが必要です。
つまり、N 秒のスライディング ウィンドウでメソッドが M 回しか実行されないようにする必要があります。
既存のクラスがわからない場合は、これをどのように実装するかについて、ソリューション/アイデアを自由に投稿してください。
M の固定サイズのタイムスタンプのリング バッファーを使用します。メソッドが呼び出されるたびに、最も古いエントリをチェックし、過去 N 秒未満の場合は実行して別のエントリを追加し、それ以外の場合はスリープします。時差のため。
私にとってすぐに使えるのは Google Guava RateLimiter でした。
// Allow one request per second
private RateLimiter throttle = RateLimiter.create(1.0);
private void someMethod() {
throttle.acquire();
// Do something
}
これはアプリケーションによって異なります。
複数のスレッドが、バーストが許可されていないグローバルにレート制限されたアクションをトークンに実行させたい場合を想像してください(つまり、10 秒あたり 10 個のアクションを制限したいが、最初の 1 秒間に 10 個のアクションが発生して残りたくない場合)。 9秒停止)。
DelayedQueue には欠点があります。スレッドがトークンを要求する順序は、要求が満たされる順序とは異なる場合があります。複数のスレッドがトークンを待ってブロックされている場合、どのスレッドが次に利用可能なトークンを取得するかは明確ではありません。私の見解では、スレッドを永遠に待機させることさえできます。
1 つの解決策は、2 つの連続するアクションの間に最小限の時間間隔を設け、要求された順序でアクションを実行することです。
実装は次のとおりです。
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);
}
}
}
あなたが尋ねたものではありませんがThreadPoolExecutor
、N 秒で M リクエストではなく M 同時リクエストに制限するように設計されている も役立つ可能性があります。
簡単なスロットリング アルゴリズムを実装しました。このリンクを 試してください。
アルゴリズムについて簡単に説明すると、
このアルゴリズムは、Java Delayed Queueの機能を利用します。予想される遅延 (ここではミリ秒のTimeUnitの 1000/M ) で遅延オブジェクトを作成します。同じオブジェクトを遅延キューに入れます。これにより、移動ウィンドウが提供されます。次に、各メソッド呼び出しがキューからオブジェクトを取得する前に、指定された遅延の後にのみ返されるブロッキング呼び出しが行われ、メソッド呼び出しの後、更新された時間 (ここでは現在のミリ秒) でオブジェクトをキューに入れることを忘れないでください。 .
ここでは、遅延が異なる複数の遅延オブジェクトを使用することもできます。このアプローチは、高いスループットも提供します。
この単純なアプローチを使用してみてください。
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();
}
}
私の解決策: シンプルな util メソッドです。変更してラッパー クラスを作成できます。
public static Runnable throttle (Runnable realRunner, long delay) {
Runnable throttleRunner = new Runnable() {
// whether is waiting to run
private boolean _isWaiting = false;
// target time to run realRunner
private long _timeToRun;
// specified delay time to wait
private long _delay = delay;
// Runnable that has the real task to run
private Runnable _realRunner = realRunner;
@Override
public void run() {
// current time
long now;
synchronized (this) {
// another thread is waiting, skip
if (_isWaiting) return;
now = System.currentTimeMillis();
// update time to run
// do not update it each time since
// you do not want to postpone it unlimited
_timeToRun = now+_delay;
// set waiting status
_isWaiting = true;
}
try {
Thread.sleep(_timeToRun-now);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// clear waiting status before run
_isWaiting = false;
// do the real task
_realRunner.run();
}
}};
return throttleRunner;
}