Hier der einfachste Algorithmus , wenn Sie Nachrichten einfach löschen möchten, wenn sie zu schnell eintreffen (anstatt sie in die Warteschlange zu stellen, was sinnvoll ist, da die Warteschlange möglicherweise beliebig groß wird):
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;
Diese Lösung enthält keine Datenstrukturen, Timer usw. und funktioniert einwandfrei :) Um dies zu sehen, wächst die Zulage mit einer Geschwindigkeit von höchstens 5/8 Einheiten pro Sekunde, dh höchstens fünf Einheiten pro acht Sekunden. Jede weitergeleitete Nachricht zieht eine Einheit ab, sodass Sie nicht mehr als fünf Nachrichten pro acht Sekunden senden können.
Beachten Sie, dass rate
dies eine Ganzzahl sein sollte, dh ohne einen Dezimalteil ungleich Null , da sonst der Algorithmus nicht richtig funktioniert (die tatsächliche Rate wird es nicht sein rate/per
). ZB rate=0.5; per=1.0;
funktioniert nicht, weil allowance
nie auf 1.0 wachsen wird. Funktioniert aber rate=1.0; per=2.0;
gut.
allowance
. Die Eimergröße istrate
. Dieallowance += …
Zeile ist eine Optimierung des Hinzufügens eines Tokens pro Rate ÷ pro Sekunde.Verwenden Sie diesen Dekorator @RateLimited (ratepersec) vor Ihrer Funktion, die in die Warteschlange gestellt wird.
Grundsätzlich wird überprüft, ob seit dem letzten Mal 1 / Rate-Sekunden vergangen sind, und wenn nicht, wird der Rest der Zeit gewartet, andernfalls wird nicht gewartet. Dies begrenzt Sie effektiv auf Rate / Sek. Der Dekorateur kann auf jede Funktion angewendet werden, für die Sie eine Geschwindigkeitsbegrenzung wünschen.
Wenn Sie in Ihrem Fall maximal 5 Nachrichten pro 8 Sekunden wünschen, verwenden Sie @RateLimited (0.625) vor Ihrer sendToQueue-Funktion.
quelle
time.clock()
hat nicht genug Auflösung auf meinem System, also habe ich den Code angepasst und auf use geänderttime.time()
time.clock()
, was die verstrichene CPU-Zeit misst. Die CPU-Zeit kann viel schneller oder langsamer als die "tatsächliche" Zeit laufen. Sie möchtentime.time()
stattdessen verwenden, was die Wandzeit ("tatsächliche" Zeit) misst.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.). Drittenslast_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).
$ 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.
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:
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).
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.
quelle
Um die Verarbeitung zu blockieren, bis die Nachricht gesendet werden kann, wodurch weitere Nachrichten in die Warteschlange gestellt werden, kann die schöne Lösung von antti auch folgendermaßen geändert werden:
Es wird nur gewartet, bis genügend Berechtigung zum Senden der Nachricht vorhanden ist. Um nicht mit dem Zweifachen des Satzes zu beginnen, kann die Zulage auch mit 0 initialisiert werden.
quelle
(1-allowance) * (per/rate)
, müssen Sie den gleichen Betrag hinzufügenlast_check
.Behalten Sie die Zeit bei, zu der die letzten fünf Zeilen gesendet wurden. Halten Sie die Nachrichten in der Warteschlange so lange, bis die fünftletzte Nachricht (falls vorhanden) mindestens 8 Sekunden in der Vergangenheit liegt (wobei last_five ein Array von Zeiten ist):
quelle
Eine Lösung besteht darin, jedem Warteschlangenelement einen Zeitstempel zuzuweisen und das Element nach Ablauf von 8 Sekunden zu verwerfen. Sie können diese Prüfung jedes Mal durchführen, wenn die Warteschlange hinzugefügt wird.
Dies funktioniert nur, wenn Sie die Warteschlangengröße auf 5 begrenzen und alle Ergänzungen verwerfen, während die Warteschlange voll ist.
quelle
Wenn noch jemand interessiert ist, verwende ich diese einfache aufrufbare Klasse in Verbindung mit einem zeitgesteuerten LRU-Schlüsselwertspeicher, um die Anforderungsrate pro IP zu begrenzen. Verwendet eine Deque, kann aber umgeschrieben werden, um stattdessen mit einer Liste verwendet zu werden.
quelle
Nur eine Python-Implementierung eines Codes aus einer akzeptierten Antwort.
quelle
Wie wäre es damit:
quelle
Ich brauchte eine Variation in Scala. Hier ist es:
So kann es verwendet werden:
quelle