web-dev-qa-db-fra.com

Java Executor avec contrôle de régulation/débit

Je recherche un exécuteur Java qui me permette de spécifier des limites de limitation, de débit et de stimulation. Par exemple, pas plus de 100 tâches peuvent être traitées en une seconde. Si davantage de tâches sont soumises, elles doivent être mises en file d'attente et exécutées ultérieurement. Le but principal de cette opération est d'éviter de rencontrer des limites lors de l'utilisation d'API ou de serveurs étrangers.

Je me demande si la base Java (dont je doute, car j'ai vérifié) ou un autre moyen fiable (Apache Commons, par exemple) le fournit, ou si je dois écrire le mien. De préférence quelque chose de léger. Cela ne me dérange pas d’écrire moi-même, mais s’il existe une version «standard» quelque part, j’aimerais au moins la regarder en premier.

23
mrip

Jetez un oeil à goyaves RateLimiter :

Un limiteur de vitesse. Conceptuellement, un limiteur de débit distribue les autorisations à un taux configurable de . Chaque acquisition () bloque si nécessaire jusqu’à ce qu’un permis Soit disponible, puis le prend. Une fois acquis, les permis ne doivent pas nécessairement être Délivrés. Les limiteurs de débit sont souvent utilisés pour limiter le débit auquel on accède à De certaines ressources physiques ou logiques. Ceci diffère de Semaphore qui limite le nombre d’accès simultanés au lieu de Taux (notez que la concurrence et le taux sont étroitement liés, , Voir par exemple Loi de Little).

Son threadsafe, mais toujours @Beta. Cela vaut peut-être la peine d'essayer de toute façon.

Vous devez encapsuler chaque appel dans la variable Executor par rapport au limiteur de débit. Pour une solution plus propre, vous pouvez créer une sorte d’emballage pour la variable ExecutorService.

Du javadoc:

 final RateLimiter rateLimiter = RateLimiter.create(2.0); // rate is "2 permits per second"
  void submitTasks(List<Runnable> tasks, Executor executor) {
    for (Runnable task : tasks) {
      rateLimiter.acquire(); // may wait
      executor.execute(task);
    }
  }
24

Java Executor n'offre pas une telle limitation, seulement une limitation en nombre de threads, ce qui n'est pas ce que vous recherchez.

En général, l'exécutant est le mauvais endroit pour limiter de telles actions, il devrait l'être au moment où le thread tente d'appeler le serveur extérieur. Vous pouvez le faire par exemple en limitant Semaphore que les threads attendent avant de soumettre leurs demandes.

Fil d'appel:

public void run() {
  // ...
  requestLimiter.acquire();
  connection.send();
  // ...
 }

En même temps, vous planifiez un thread (unique) secondaire pour libérer périodiquement (comme toutes les 60 secondes) les ressources acquises:

 public void run() {
  // ...
  requestLimiter.drainPermits();  // make sure not more than max are released by draining the Semaphore empty
  requestLimiter.release(MAX_NUM_REQUESTS);
  // ...
 }
6
TwoThe

pas plus de 100 tâches peuvent être traitées en une seconde - si plus de tâches sont soumises, elles doivent être mises en file d'attente et exécutées plus tard 

Vous devez examiner Executors.newFixedThreadPool(int limit). Cela vous permettra de limiter le nombre de threads pouvant être exécutés simultanément. Si vous soumettez plusieurs threads, ils seront mis en file d'attente et exécutés ultérieurement. 

ExecutorService threadPool = Executors.newFixedThreadPool(100);
Future<?> result1 =  threadPool.submit(runnable1);
Future<?> result2 = threadPool.submit(runnable2);
Futurte<SomeClass> result3 = threadPool.submit(callable1);  
...  

L'extrait ci-dessus montre comment vous travailleriez avec une ExecutorService qui ne permet pas l'exécution simultanée de plus de 100 threads. 

Mettre à jour:
Après avoir passé en revue les commentaires, voici ce que j’ai trouvé (un peu stupide). Pourquoi ne pas garder manuellement une trace des threads à exécuter? Que diriez-vous de les stocker d'abord dans une ArrayList et de les soumettre ensuite à la Executor en fonction du nombre de threads déjà exécutés dans la dernière seconde?
Donc, supposons que 200 tâches aient été soumises dans notre ArrayList maintenue, nous pouvons itérer et ajouter 100 à la Executor. Quand une seconde passe, nous pouvons ajouter quelques threads supplémentaires en fonction du nombre de processus terminés dans la variable Executor et ainsi de suite

3
Little Child

Selon le scénario et comme suggéré dans l'une des réponses précédentes, les fonctionnalités de base d'un ThreadPoolExecutor peuvent faire l'affaire.

Mais si le pool de threads est partagé par plusieurs clients et que vous souhaitez limiter, limiter l'utilisation de chacun d'eux, en veillant à ce qu'un client n'utilise pas tous les threads, un exécutant BoundedExecutor se chargera du travail.

Plus de détails peuvent être trouvés dans l'exemple suivant:

http://jcip.net/listings/BoundedExecutor.Java

1
Joseph M. Dion

Personnellement, j'ai trouvé ce scénario assez intéressant. Dans mon cas, je voulais souligner que la phase intéressante de limitation est la phase consommatrice, comme dans la théorie classique des producteurs/consommateurs classiques. C’est le contraire de certaines des réponses suggérées précédemment. En d’autres termes, nous ne voulons pas bloquer le thread qui soumet, mais bloquer les threads consommateurs en fonction d’une stratégie de débit (tâches/seconde). Ainsi, même si des tâches sont prêtes dans la file d'attente, l'exécution/la consommation de threads peut bloquer l'attente de la stratégie de throtle.

Cela dit, je pense qu'un bon candidat serait le groupe Executors.newScheduledThreadPool (int corePoolSize). De cette façon, vous auriez besoin d'une simple file d'attente devant l'exécutant (un simple LinkedBlockingQueue conviendrait), puis d'une tâche périodique pour sélectionner les tâches réelles dans la file d'attente (ScheduledExecutorService.scheduleAtFixedRate). La solution n'est donc pas simple, mais elle devrait être assez performante si vous essayez d'étouffer les consommateurs, comme indiqué précédemment.

0
Jaime Casero