Ein Token Bucket ist ziemlich einfach zu implementieren.
Beginnen Sie mit einem Eimer mit 5 Token.
Alle 5/8 Sekunden: Wenn der Eimer weniger als 5 Token enthält, fügen Sie einen hinzu.
Jedes Mal, wenn Sie eine Nachricht senden möchten: Wenn der Bucket ≥1 Token hat, nehmen Sie ein Token heraus und senden Sie die Nachricht. Andernfalls warten Sie / lassen Sie die Nachricht fallen / was auch immer.
(Natürlich würden Sie im tatsächlichen Code einen Ganzzahlzähler anstelle von echten Token verwenden und Sie können den Schritt alle 5/8 durch Optimieren von Zeitstempeln optimieren.)
Wenn Sie die Frage noch einmal lesen und alle 8 Sekunden das Ratenlimit vollständig zurückgesetzt haben, finden Sie hier eine Änderung:
Beginnen Sie mit einem Zeitstempel vor last_send
langer Zeit (z. B. in der Epoche). Beginnen Sie auch mit demselben 5-Token-Bucket.
Schlagen Sie die Regel alle 5/8 Sekunden an.
Jedes Mal, wenn Sie eine Nachricht senden: Überprüfen Sie zunächst, ob vor last_send
≥ 8 Sekunden. Wenn ja, füllen Sie den Eimer (stellen Sie ihn auf 5 Token ein). Zweitens, wenn sich Token im Bucket befinden, senden Sie die Nachricht (andernfalls drop / wait / etc.). Drittens last_send
auf jetzt einstellen .
Das sollte für dieses Szenario funktionieren.
Ich habe tatsächlich einen IRC-Bot mit einer Strategie wie dieser geschrieben (der erste Ansatz). Es ist in Perl, nicht in Python, aber hier ist ein Code zur Veranschaulichung:
Der erste Teil behandelt das Hinzufügen von Token zum Eimer. Sie können die Optimierung des Hinzufügens von Token basierend auf der Zeit (2. bis letzte Zeile) sehen und dann klemmt die letzte Zeile den Bucket-Inhalt auf das 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 ist eine Datenstruktur, die herumgereicht wird. Dies ist Teil einer Methode, die routinemäßig ausgeführt wird (sie berechnet, wann sie das nächste Mal etwas zu tun hat, und schläft entweder so lange oder bis sie Netzwerkverkehr erhält). Der nächste Teil der Methode behandelt das Senden. Dies ist ziemlich kompliziert, da Nachrichten Prioritäten zugeordnet sind.
# 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] = [];
Das ist die erste Warteschlange, die auf jeden Fall ausgeführt wird. Auch wenn dadurch unsere Verbindung wegen Überschwemmungen zerstört wird. Wird für äußerst wichtige Dinge verwendet, z. B. zum Antworten auf den PING des Servers. Als nächstes die restlichen Warteschlangen:
# 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}});
}
}
}
Schließlich wird der Bucket-Status wieder in der $ conn-Datenstruktur gespeichert (tatsächlich etwas später in der Methode; er berechnet zunächst, wie schnell mehr Arbeit vorhanden sein wird).
# Save status.
$conn->{fujiko_limit_bucket} = $bucket;
$conn->{fujiko_limit_lasttx} = $start_time;
Wie Sie sehen können, ist der tatsächliche Code für die Bucket-Behandlung sehr klein - ungefähr vier Zeilen. Der Rest des Codes ist die Prioritätswarteschlangenbehandlung. Der Bot hat Prioritätswarteschlangen, so dass beispielsweise jemand, der mit ihm chattet, ihn nicht daran hindern kann, seine wichtigen Kick / Ban-Aufgaben zu erfüllen.