Appels de méthode de limitation à M requêtes en N secondes

J’ai besoin d’un composant / classe qui limite l’exécution de certaines méthodes au maximum de M appels en N secondes (ou ms ou nanos, peu importe).

En d’autres termes, je dois m’assurer que ma méthode n’est pas exécutée plus de M fois dans une fenêtre glissante de N secondes.

Si vous ne connaissez pas la classe existante, n’hésitez pas à poster vos solutions / idées sur la manière dont vous allez l’implémenter.

    J’utiliserais un tampon circulaire d’horodatage avec une taille fixe de M. Chaque fois que la méthode est appelée, vous vérifiez l’entrée la plus ancienne, et si elle est inférieure à N secondes, vous exécutez et ajoutez une autre entrée, sinon vous dormez pour la différence de temps.

    Ce qui a fonctionné pour moi était Google Guava RateLimiter .

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

    Concrètement, vous devriez pouvoir l’implémenter avec une DelayQueue . Initialiser la queue avec M instances Delayed avec leur délai initialement défini sur zéro. Au fur et à mesure de l’entrée des requêtes à la méthode, take un jeton, ce qui provoque le blocage de la méthode jusqu’à ce que l’exigence de limitation soit satisfaite. Lorsqu’un jeton a été pris, add un nouveau jeton à la queue avec un délai de N

    Lisez sur l’algorithme du seau à jetons . Fondamentalement, vous avez un seau avec des jetons. Chaque fois que vous exécutez la méthode, vous prenez un jeton. S’il n’y a plus de jetons, vous bloquez jusqu’à en avoir un. Pendant ce temps, il existe des acteurs externes qui réapprovisionnent les jetons à un intervalle fixe.

    Je ne suis pas au courant d’une bibliothèque pour faire cela (ou quelque chose de similaire). Vous pouvez écrire cette logique dans votre code ou utiliser AspectJ pour append le comportement.

    Cela dépend de l’application.

    Imaginez le cas où plusieurs threads veulent un jeton pour effectuer une action globalement limitée en termes de taux sans rafale autorisée (c.-à-d. Vous voulez limiter 10 actions par 10 secondes mais vous ne voulez pas que 10 actions se produisent pendant la première seconde) 9 secondes arrêtées).

    Le DelayedQueue a un inconvénient: l’ordre dans lequel les threads demandent des jetons peut ne pas être l’ordre dans lequel leur requête est satisfaite. Si plusieurs threads sont bloqués dans l’attente d’un jeton, il n’est pas clair lequel prend le prochain jeton disponible. Vous pourriez même avoir des discussions en attente pour toujours, à mon sharepoint vue.

    Une solution consiste à disposer d’ un intervalle de temps minimum entre deux actions consécutives et à prendre des mesures dans le même ordre que celui demandé.

    Voici une implémentation:

     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 vous avez besoin d’un limiteur de taux de fenêtre glissante basé sur Java qui fonctionnera sur un système dissortingbué, vous pouvez consulter le projet https://github.com/mokies/ratelimitj .

    Une configuration sauvegardée par Redis, pour limiter les demandes par IP à 50 par minute, ressemblerait à ceci:

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

    Voir https://github.com/mokies/ratelimitj/tree/master/ratelimitj-redis pour plus de détails sur la configuration de Redis.

    Bien que ce ne soit pas ce que vous avez demandé, ThreadPoolExecutor , conçu pour ThreadPoolExecutor requêtes simultanées à la place des requêtes M en N secondes, pourrait également être utile.

    La question originale ressemble beaucoup au problème résolu dans cet article: Java Asynchronous Throttler .

    Pour un taux d’appels en N secondes, le régulateur décrit dans ce blog garantit que tout intervalle de longueur N sur la ligne de temps ne contiendra pas plus de M appels.

    J’ai implémenté un algorithme de limitation simple. Essayez ce lien, http://krishnaprasadas.blogspot.in/2012/05/throttling-algorithm.html

    Un brief sur l’algorithme,

    Cet algorithme utilise la capacité de Java Delayed Queue . Créez un object retardé avec le délai attendu (ici 1000 / M pour TimeUnit en milliseconde). Placez le même object dans la queue différée qui fournira la fenêtre mobile pour nous. Alors avant que chaque appel de méthode prenne l’object de la queue, Take est un appel bloquant qui ne retournera qu’après le délai spécifié, et après l’appel de la méthode, n’oubliez pas de mettre l’object dans la file avec l’heure mise à jour (ici millisecondes actuelles) .

    Ici, nous pouvons également avoir plusieurs objects retardés avec un délai différent. Cette approche fournira également un débit élevé.

    Je dois m’assurer que ma méthode n’est pas exécutée plus de M fois dans une fenêtre glissante de N secondes.

    J’ai récemment écrit un blog sur la façon de le faire dans .NET. Vous pourriez être en mesure de créer quelque chose de similaire en Java.

    Meilleure limitation des taux dans .NET

    Essayez d’utiliser cette approche 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 prend également en charge le mécanisme Throttler comme suit:

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

    Vous pouvez utiliser redis pour cela lorsque le locking est nécessaire dans le système dissortingbué. Deuxième algorithme dans https://redis.io/commands/incr

    Ceci est une mise à jour du code LeakyBucket ci-dessus. Cela fonctionne pour plus de 1000 requêtes par seconde.

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

    et le plus acharné pour ci-dessus:

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

    Découvrez la classe [TimerTask 1 . Ou le ScheduledExecutor .