Un bucket token è abbastanza semplice da implementare.
Inizia con un secchio con 5 token.
Ogni 5/8 secondi: se il bucket ha meno di 5 token, aggiungine uno.
Ogni volta che si desidera inviare un messaggio: se il bucket ha un token ≥1, estrarre un token e inviare il messaggio. Altrimenti, attendi / rilascia il messaggio / qualunque cosa.
(ovviamente, nel codice reale, useresti un contatore intero invece di token reali e puoi ottimizzare ogni passo di 5/8 memorizzando i timestamp)
Rileggendo nuovamente la domanda, se il limite di velocità viene completamente ripristinato ogni 8 secondi, ecco una modifica:
Inizia con un timestamp, last_send
molto tempo fa (ad esempio, in epoca). Inoltre, inizia con lo stesso bucket a 5 token.
Colpisci la regola ogni 5/8 secondi.
Ogni volta che invii un messaggio: per prima cosa controlla se last_send
≥ 8 secondi fa. In tal caso, riempi il secchio (impostalo su 5 token). In secondo luogo, se ci sono token nel bucket, invia il messaggio (altrimenti, drop / wait / etc.). Terzo, impostato last_send
su ora.
Dovrebbe funzionare per quello scenario.
In realtà ho scritto un bot IRC usando una strategia come questa (il primo approccio). È in Perl, non in Python, ma ecco un codice per illustrare:
La prima parte qui gestisce l'aggiunta di token al bucket. Puoi vedere l'ottimizzazione dell'aggiunta di token in base al tempo (dalla 2a all'ultima riga) e quindi l'ultima riga blocca il contenuto del bucket al massimo (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 è una struttura di dati che viene passata in giro. Questo è all'interno di un metodo che viene eseguito di routine (calcola quando la prossima volta avrà qualcosa da fare e dorme così a lungo o fino a quando non riceve traffico di rete). La parte successiva del metodo gestisce l'invio. È piuttosto complicato, perché i messaggi hanno priorità associate a loro.
# 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] = [];
Questa è la prima coda, che viene eseguita indipendentemente da cosa. Anche se la nostra connessione viene uccisa per inondazioni. Utilizzato per cose estremamente importanti, come rispondere al PING del server. Successivamente, il resto delle code:
# 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}});
}
}
}
Infine, lo stato del bucket viene salvato nella struttura di dati $ conn (in realtà un po 'più tardi nel metodo; in primo luogo calcola quanto presto avrà più lavoro)
# Save status.
$conn->{fujiko_limit_bucket} = $bucket;
$conn->{fujiko_limit_lasttx} = $start_time;
Come puoi vedere, il vero codice di gestione della benna è molto piccolo - circa quattro righe. Il resto del codice è la gestione delle code prioritarie. Il bot ha le code prioritarie in modo che, ad esempio, qualcuno che chiacchiera con esso non possa impedirgli di svolgere le sue importanti funzioni di kick / ban.