Voici l' algorithme le plus simple , si vous souhaitez simplement supprimer les messages lorsqu'ils arrivent trop rapidement (au lieu de les mettre en file d'attente, ce qui est logique car la file d'attente peut devenir arbitrairement grande):
rate = 5.0; // unit: messages
per = 8.0; // unit: seconds
allowance = rate; // unit: messages
last_check = now(); // floating-point, e.g. usec accuracy. Unit: seconds
when (message_received):
current = now();
time_passed = current - last_check;
last_check = current;
allowance += time_passed * (rate / per);
if (allowance > rate):
allowance = rate; // throttle
if (allowance < 1.0):
discard_message();
else:
forward_message();
allowance -= 1.0;
Il n'y a pas de structures de données, de temporisateurs, etc. dans cette solution et cela fonctionne proprement :) Pour voir cela, «allocation» croît à une vitesse de 5/8 unités par seconde au plus, soit au plus cinq unités par huit secondes. Chaque message transféré déduit une unité, vous ne pouvez donc pas envoyer plus de cinq messages toutes les huit secondes.
Notez que cela rate
devrait être un entier, c'est-à-dire sans partie décimale non nulle, sinon l'algorithme ne fonctionnera pas correctement (le taux réel ne le sera pas rate/per
). Par exemple, rate=0.5; per=1.0;
ne fonctionne pas car allowance
il ne passera jamais à 1.0. Mais rate=1.0; per=2.0;
fonctionne très bien.
allowance
. La taille du godet estrate
. Laallowance += …
ligne est une optimisation de l'ajout d'un jeton à chaque taux ÷ par seconde.Utilisez ce décorateur @RateLimited (ratepersec) avant votre fonction qui met en file d'attente.
Fondamentalement, cela vérifie si 1 / rate sec s'est écoulé depuis la dernière fois et sinon, attend le reste du temps, sinon il n'attend pas. Cela vous limite effectivement au taux / sec. Le décorateur peut être appliqué à n'importe quelle fonction que vous souhaitez limiter au taux.
Dans votre cas, si vous voulez un maximum de 5 messages toutes les 8 secondes, utilisez @RateLimited (0,625) avant votre fonction sendToQueue.
la source
time.clock()
n'a pas assez de résolution sur mon système, j'ai donc adapté le code et changé pour l'utilisertime.time()
time.clock()
, qui mesure le temps CPU écoulé. Le temps CPU peut être beaucoup plus rapide ou beaucoup plus lent que le temps "réel". Vous voulez utiliser à latime.time()
place, qui mesure le temps du mur (temps "réel").Un Token Bucket est assez simple à implémenter.
Commencez avec un seau avec 5 jetons.
Toutes les 5/8 secondes: si le compartiment contient moins de 5 jetons, ajoutez-en un.
Chaque fois que vous voulez envoyer un message: Si le seau a ≥1 jeton, retirez un jeton et envoyez le message. Sinon, attendez / laissez tomber le message / peu importe.
(évidemment, dans le code réel, vous utiliseriez un compteur entier au lieu de vrais jetons et vous pouvez optimiser le pas toutes les 5 / 8s en stockant les horodatages)
En relisant la question, si la limite de débit est entièrement réinitialisée toutes les 8 secondes, alors voici une modification:
Commencez par un horodatage,,
last_send
à un moment il y a longtemps (par exemple, à l'époque). Commencez également avec le même seau à 5 jetons.Frappez la règle toutes les 5/8 secondes.
Chaque fois que vous envoyez un message: Vérifiez d'abord s'il y a
last_send
≥ 8 secondes. Si tel est le cas, remplissez le seau (définissez-le sur 5 jetons). Deuxièmement, s'il y a des jetons dans le compartiment, envoyez le message (sinon, déposez / attendez / etc.). Troisièmement, réglélast_send
maintenant.Cela devrait fonctionner pour ce scénario.
J'ai en fait écrit un bot IRC en utilisant une stratégie comme celle-ci (la première approche). C'est en Perl, pas en Python, mais voici du code pour illustrer:
La première partie ici gère l'ajout de jetons au bucket. Vous pouvez voir l'optimisation de l'ajout de jetons en fonction du temps (de la 2e à la dernière ligne), puis la dernière ligne limite le contenu du seau au maximum (MESSAGE_BURST)
$ conn est une structure de données qui est transmise. C'est à l'intérieur d'une méthode qui s'exécute régulièrement (elle calcule quand la prochaine fois qu'elle aura quelque chose à faire et dort aussi longtemps ou jusqu'à ce qu'elle reçoive du trafic réseau). La partie suivante de la méthode gère l'envoi. C'est plutôt compliqué, car les messages sont associés à des priorités.
C'est la première file d'attente, qui est exécutée quoi qu'il arrive. Même si cela fait tuer notre connexion pour une inondation. Utilisé pour des choses extrêmement importantes, comme répondre au PING du serveur. Ensuite, le reste des files d'attente:
Enfin, l'état du bucket est sauvegardé dans la structure de données $ conn (en fait un peu plus tard dans la méthode; il calcule d'abord combien de temps il aura plus de travail)
Comme vous pouvez le voir, le code de gestion du bucket est très petit - environ quatre lignes. Le reste du code est la gestion de la file d'attente prioritaire. Le bot a des files d'attente prioritaires afin que, par exemple, quelqu'un qui discute avec lui ne puisse pas l'empêcher de faire ses importantes tâches de kick / ban.
la source
pour bloquer le traitement jusqu'à ce que le message puisse être envoyé, mettant ainsi d'autres messages en file d'attente, la belle solution d'antti peut également être modifiée comme ceci:
il attend juste qu'il y ait suffisamment de marge pour envoyer le message. pour ne pas commencer avec deux fois le taux, la tolérance peut également être initialisée à 0.
la source
(1-allowance) * (per/rate)
, vous devez ajouter la même quantitélast_check
.Conservez l'heure à laquelle les cinq dernières lignes ont été envoyées. Maintenez les messages en file d'attente jusqu'à ce que le cinquième message le plus récent (s'il existe) soit au moins 8 secondes dans le passé (avec last_five comme tableau de fois):
la source
Une solution consiste à attacher un horodatage à chaque élément de la file d'attente et à supprimer l'élément au bout de 8 secondes. Vous pouvez effectuer cette vérification à chaque fois que la file d'attente est ajoutée.
Cela ne fonctionne que si vous limitez la taille de la file d'attente à 5 et annulez tout ajout alors que la file d'attente est pleine.
la source
Si quelqu'un est toujours intéressé, j'utilise cette classe appelable simple en conjonction avec un stockage de valeur de clé LRU chronométré pour limiter le taux de demande par IP. Utilise un deque, mais peut être réécrit pour être utilisé avec une liste à la place.
la source
Juste une implémentation python d'un code de réponse acceptée.
la source
Que dis-tu de ça:
la source
J'avais besoin d'une variante de Scala. C'est ici:
Voici comment il peut être utilisé:
la source