Відро Token - це досить просто у виконанні.
Почніть з відра з 5 жетонами.
Кожні 5/8 секунд: Якщо у відрі менше 5 жетонів, додайте його.
Щоразу, коли ви бажаєте надіслати повідомлення: Якщо у відрі є ≥1 маркер, вийміть один маркер і надішліть повідомлення. В іншому випадку зачекайте / відпустіть повідомлення / що завгодно.
(очевидно, що в фактичному коді ви використовуєте цілий лічильник замість реальних маркерів, і ви можете оптимізувати кожні 5/8-й крок, зберігаючи часові позначки)
Прочитавши ще раз питання, якщо обмеження швидкості повністю скидається кожні 8 секунд, то тут є модифікація:
Почніть з мітки часу, last_send
давно, колись (наприклад, в епоху). Також почніть з того ж відра з 5 жетонами.
Страйкуйте кожні 5/8 секунд.
Кожен раз, коли ви надсилаєте повідомлення: Спочатку перевірте, чи не було last_send
≥ 8 секунд тому. Якщо це так, заповніть відро (встановіть його на 5 жетонів). По-друге, якщо у відрі є лексеми, надішліть повідомлення (інакше, drop / wait / тощо). По-третє, встановлено last_send
зараз.
Це повинно працювати за таким сценарієм.
Я фактично написав бота IRC, використовуючи таку стратегію (перший підхід). Його в Perl, а не в Python, але ось який-небудь код для ілюстрації:
Перша частина тут обробляє додавання жетонів до відра. Ви можете побачити оптимізацію додавання жетонів на основі часу (2-й до останнього рядка), а потім останній рядок максимально затискає вміст відра (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 - це структура даних, яка передається навколо. Це всередині методу, який працює звичайно (він обчислює, коли наступного разу щось буде робити, і спить або так довго, поки не отримає мережевий трафік). Наступна частина методу обробляє надсилання. Це досить складно, оскільки повідомлення мають пріоритети, пов'язані з ними.
# 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] = [];
Це перша черга, яка запускається незалежно від того. Навіть якщо це стане причиною того, що наше з'єднання загине за затоплення. Використовується для надзвичайно важливих речей, таких як відповідь на PING сервера. Далі, решта черг:
# 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}});
}
}
}
Нарешті, статус відра зберігається назад у структурі даних $ conn (фактично трохи пізніше методу; він спочатку обчислює, як скоро у нього з’явиться більше роботи)
# Save status.
$conn->{fujiko_limit_bucket} = $bucket;
$conn->{fujiko_limit_lasttx} = $start_time;
Як бачите, фактичний код обробки відра дуже малий - приблизно чотири рядки. Решта коду - це пріоритетна робота з чергою. У бота є черги з пріоритетом, так що, наприклад, хтось із ним в чаті не може перешкодити виконувати свої важливі обов'язки щодо удару / заборони.