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)
my $start_time = time;
...
# Bucket handling
my $bucket = $conn->{fujiko_limit_bucket};
my $lasttx = $conn->{fujiko_limit_lasttx};
$bucket += ($start_time-$lasttx)/MESSAGE_INTERVAL;
($bucket <= MESSAGE_BURST) or $bucket = 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.
# Queue handling. Start with the ultimate queue.
my $queues = $conn->{fujiko_queues};
foreach my $entry (@{$queues->[PRIORITY_ULTIMATE]}) {
# Ultimate is special. We run ultimate no matter what. Even if
# it sends the bucket negative.
--$bucket;
$entry->{code}(@{$entry->{args}});
}
$queues->[PRIORITY_ULTIMATE] = [];
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:
# Continue to the other queues, in order of priority.
QRUN: for (my $pri = PRIORITY_HIGH; $pri >= PRIORITY_JUNK; --$pri) {
my $queue = $queues->[$pri];
while (scalar(@$queue)) {
if ($bucket < 1) {
# continue later.
$need_more_time = 1;
last QRUN;
} else {
--$bucket;
my $entry = shift @$queue;
$entry->{code}(@{$entry->{args}});
}
}
}
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)
# Save status.
$conn->{fujiko_limit_bucket} = $bucket;
$conn->{fujiko_limit_lasttx} = $start_time;
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.