Token Bucket jest dość prosty do zaimplementowania.
Zacznij od wiadra z 5 tokenami.
Co 5/8 sekund: jeśli w wiadrze jest mniej niż 5 żetonów, dodaj jeden.
Za każdym razem, gdy chcesz wysłać wiadomość: Jeśli wiadro ma ≥1 token, wyjmij jeden token i wyślij wiadomość. W przeciwnym razie poczekaj / upuść wiadomość / cokolwiek.
(oczywiście w rzeczywistym kodzie używałbyś licznika liczb całkowitych zamiast prawdziwych tokenów i możesz zoptymalizować co 5 / 8s krok, przechowując znaczniki czasu)
Czytając ponownie pytanie, jeśli limit szybkości jest całkowicie resetowany co 8 sekund, oto modyfikacja:
Zacznij od sygnatury last_send
czasowej dawno temu (np. W epoce). Zacznij też od tego samego zasobnika składającego się z 5 tokenów.
Zastosuj regułę co 5/8 sekund.
Za każdym razem, gdy wysyłasz wiadomość: Najpierw sprawdź, czy last_send
≥ 8 sekund temu. Jeśli tak, napełnij wiadro (ustaw na 5 żetonów). Po drugie, jeśli w wiadrze znajdują się tokeny, wyślij wiadomość (w przeciwnym razie upuść / czekaj / itp.). Po trzecie, ustawione last_send
na teraz.
To powinno działać w tym scenariuszu.
Właściwie napisałem bota IRC używając takiej strategii (pierwsze podejście). Jest w Perlu, nie w Pythonie, ale oto kod do zilustrowania:
Pierwsza część dotyczy dodawania tokenów do wiadra. Możesz zobaczyć optymalizację dodawania tokenów na podstawie czasu (od drugiej do ostatniej linii), a następnie ostatnia linia ogranicza zawartość wiadra do maksimum (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 to przekazywana struktura danych. Jest to metoda, która działa rutynowo (oblicza, kiedy następnym razem będzie mieć coś do zrobienia, i śpi tak długo lub do momentu, gdy uzyska ruch w sieci). Kolejna część metody zajmuje się wysyłaniem. Jest to dość skomplikowane, ponieważ wiadomości mają przypisane priorytety.
# 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] = [];
To pierwsza kolejka, która jest uruchamiana bez względu na wszystko. Nawet jeśli spowoduje to utratę połączenia z powodu zalania. Używane do niezwykle ważnych rzeczy, takich jak odpowiadanie na PING serwera. Następnie pozostałe kolejki:
# 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}});
}
}
}
Wreszcie status zasobnika jest zapisywany z powrotem w strukturze danych $ conn (właściwie nieco później w metodzie; najpierw oblicza, jak szybko będzie miał więcej pracy)
# Save status.
$conn->{fujiko_limit_bucket} = $bucket;
$conn->{fujiko_limit_lasttx} = $start_time;
Jak widać, rzeczywisty kod obsługi zasobnika jest bardzo mały - około czterech wierszy. Pozostała część kodu to priorytetowa obsługa kolejek. Bot ma kolejki priorytetowe, więc np. Ktoś z nim rozmawiający nie może przeszkodzić mu w wykonywaniu ważnych obowiązków związanych z wyrzucaniem / banowaniem.