Um Token Bucket é bastante simples de implementar.
Comece com um balde com 5 fichas.
A cada 5/8 segundos: se o balde tiver menos de 5 tokens, adicione um.
Sempre que você desejar enviar uma mensagem: Se o bucket tiver ≥1 token, retire um token e envie a mensagem. Caso contrário, aguarde / solte a mensagem / o que for.
(obviamente, no código real, você usaria um contador inteiro em vez de tokens reais e poderá otimizar a cada etapa de 5 / 8s armazenando registros de data e hora)
Lendo a pergunta novamente, se o limite da taxa for totalmente redefinido a cada 8 segundos, eis uma modificação:
Comece com um registro de data e hora, last_send
há muito tempo (por exemplo, na época). Além disso, comece com o mesmo balde de 5 token.
Atinja a regra a cada 5/8 segundos.
Cada vez que você envia uma mensagem: Primeiro, verifique se há last_send
≥ 8 segundos. Nesse caso, preencha o balde (configure-o para 5 fichas). Segundo, se houver tokens no bucket, envie a mensagem (caso contrário, solte / aguarde / etc.). Terceiro, defina last_send
agora.
Isso deve funcionar para esse cenário.
Na verdade, eu escrevi um bot de IRC usando uma estratégia como essa (a primeira abordagem). Está em Perl, não em Python, mas aqui está um código para ilustrar:
A primeira parte aqui trata da adição de tokens ao balde. Você pode ver a otimização da adição de tokens com base no tempo (da segunda à última linha) e, em seguida, a última linha prende o conteúdo do depósito ao máximo (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 é uma estrutura de dados que é distribuída. Isso está dentro de um método que é executado rotineiramente (calcula quando da próxima vez que tiver algo a fazer e dorme por tanto tempo ou até obter tráfego de rede). A próxima parte do método trata do envio. É um pouco complicado, porque as mensagens têm prioridades associadas a elas.
# 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] = [];
Essa é a primeira fila, que é executada, não importa o quê. Mesmo que nossa conexão seja morta por inundações. Usado para coisas extremamente importantes, como responder ao PING do servidor. Em seguida, o restante das filas:
# 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}});
}
}
}
Por fim, o status do bucket é salvo de volta na estrutura de dados $ conn (na verdade, um pouco mais tarde no método; ele primeiro calcula quanto tempo terá mais trabalho)
# Save status.
$conn->{fujiko_limit_bucket} = $bucket;
$conn->{fujiko_limit_lasttx} = $start_time;
Como você pode ver, o código real de manipulação de bucket é muito pequeno - cerca de quatro linhas. O restante do código é manipulação de fila prioritária. O bot tem filas prioritárias, de modo que, por exemplo, alguém conversando com ele não pode impedi-lo de executar suas importantes tarefas de chute / banimento.