java - programacion - Llamadas de método de aceleración a solicitudes M en N segundos
programacion android pdf 2018 (15)
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.
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.
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.
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
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));
}
}
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);
}
}
}
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 delayed con el retraso esperado (aquí 1000 / M para TimeUnit 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, take el objeto de la cola, tome es una llamada de bloqueo 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.
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();
}
}
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.
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.
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
}
Puede usar redis para esto cuando se necesita bloqueo en el sistema distribuido. Segundo algoritmo en https://redis.io/commands/incr
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<LimitRule> 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.
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
Vea la clase [TimerTask 1 . O el ScheduledExecutor .
Apache Camel también es compatible con el mecanismo Throttler siguiente manera:
from("seda:a").throttle(100).asyncDelayed().to("seda:b");