Llamadas de método de aceleración a solicitudes M en N segundos

Necesito un componente / clase que acelere la ejecución de algún método para un máximo de llamadas M en N segundos (o ms o nanos, no importa).

En otras palabras, necesito asegurarme de que mi método se ejecute no más de M veces en una ventana deslizante de N segundos.

Si no conoce la clase existente, no dude en publicar sus soluciones / ideas sobre cómo implementarla.

Utilizaría un búfer anular de sellos de tiempo con un tamaño fijo de M. Cada vez que se llama al método, se marca la entrada más antigua, y si es menor que N segundos en el pasado, se ejecuta y se agrega otra entrada; de lo contrario, se duerme. por la diferencia de tiempo

Lo que funcionó de la caja para mí fue Google Guava RateLimiter .

// Allow one request per second private RateLimiter throttle = RateLimiter.create(1.0); private void someMethod() { throttle.acquire(); // Do something } 

En términos concretos, debería poder implementar esto con un DelayQueue . Inicialice la cola con M Instancias Delayed con su retraso inicialmente establecido en cero. A medida que ingresen las solicitudes al método, take un token, que hace que el método se bloquee hasta que se cumpla el requisito de aceleración. Cuando se haya tomado un token, add un nuevo token a la cola con un retraso de N

Lea sobre el algoritmo del bloque Token . Básicamente, tienes un cubo con tokens. Cada vez que ejecutas el método, tomas un token. Si no hay más tokens, bloquee hasta que obtenga uno. Mientras tanto, hay un actor externo que reabastece los tokens en un intervalo fijo.

No conozco una biblioteca para hacer esto (ni nada similar). Puede escribir esta lógica en su código o usar AspectJ para agregar el comportamiento.

Esto depende de la aplicación.

Imagine el caso en el que varios subprocesos quieren que un token realice una acción de velocidad limitada global sin ráfaga permitida (es decir, desea limitar 10 acciones por 10 segundos pero no desea que sucedan 10 acciones en el primer segundo y luego permanecer 9 segundos detenido).

El DelayedQueue tiene una desventaja: el orden en el que los hilos solicitan los tokens puede no ser el orden en el que obtienen su solicitud. Si se bloquean varios hilos esperando un token, no está claro cuál tomará el siguiente token disponible. Incluso podría tener hilos esperando por siempre, en mi punto de vista.

Una solución es tener un intervalo mínimo de tiempo entre dos acciones consecutivas , y tomar acciones en el mismo orden en que fueron solicitadas.

Aquí hay una implementación:

 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); } } } 

Si necesita un limitador de velocidad de ventana deslizante basado en Java que operará a través de un sistema distribuido, le recomendamos echar un vistazo al proyecto https://github.com/mokies/ratelimitj .

Una configuración respaldada por Redis, para limitar las solicitudes por IP a 50 por minuto se vería así:

 import com.lambdaworks.redis.RedisClient; import es.moki.ratelimitj.core.LimitRule; RedisClient client = RedisClient.create("redis://localhost"); Set rules = Collections.singleton(LimitRule.of(1, TimeUnit.MINUTES, 50)); // 50 request per minute, per key RedisRateLimit requestRateLimiter = new RedisRateLimit(client, rules); boolean overLimit = requestRateLimiter.overLimit("ip:127.0.0.2"); 

Consulte https://github.com/mokies/ratelimitj/tree/master/ratelimitj-redis para obtener más detalles sobre la configuración de Redis.

Aunque no es lo que usted solicitó, ThreadPoolExecutor , que está diseñado para ThreadPoolExecutor M solicitudes simultáneas en lugar de M solicitudes en N segundos, también podría ser útil.

La pregunta original se parece mucho al problema resuelto en esta publicación de blog: Java Multi-Channel Asynchronous Throttler .

Para una tasa de M llamadas en N segundos, el regulador descrito en este blog garantiza que cualquier intervalo de longitud N en la línea de tiempo no contendrá más de M llamadas.

Implementé un algoritmo de aceleración simple. Pruebe este enlace, http://krishnaprasadas.blogspot.in/2012/05/throttling-algorithm.html

Un breve sobre el algoritmo,

Este algoritmo utiliza la capacidad de Java Delayed Queue . Cree un objeto retrasado con el retraso esperado (aquí 1000 / M para Time Unit de milisegundos). Coloque el mismo objeto en la cola retrasada, que el interno proporcionará la ventana móvil para nosotros. Luego, antes de cada llamada al método, tome el objeto de la cola, tome es una llamada de locking que volverá solo después de la demora especificada, y después de la llamada al método no olvide poner el objeto en la cola con la hora actualizada (aquí milisegundos actuales) .

Aquí también podemos tener múltiples objetos retrasados ​​con diferentes demoras. Este enfoque también proporcionará un alto rendimiento.

Necesito asegurarme de que mi método se ejecute no más de M veces en una ventana deslizante de N segundos.

Recientemente escribí una publicación de blog sobre cómo hacer esto en .NET. Es posible que pueda crear algo similar en Java.

Mejor límite de velocidad en .NET

Intenta usar este enfoque simple:

 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(); } 

}

Apache Camel también es compatible con el mecanismo Throttler de la siguiente manera:

 from("seda:a").throttle(100).asyncDelayed().to("seda:b"); 

Puede usar redis para esto cuando se necesita locking en el sistema distribuido. Segundo algoritmo en https://redis.io/commands/incr

Esta es una actualización del código LeakyBucket anterior. Esto funciona para más de 1000 solicitudes por segundo.

 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); } } 

y la prueba unitaria para arriba:

 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)); } } 

Vea la clase [TimerTask 1 . O el ScheduledExecutor .