Un Token Bucket es bastante simple de implementar.
Comience con un cubo con 5 fichas.
Cada 5/8 segundos: si el cubo tiene menos de 5 tokens, agrega uno.
Cada vez que desee enviar un mensaje: si el depósito tiene ≥1 ficha, saque una ficha y envíe el mensaje. De lo contrario, espere / suelte el mensaje / lo que sea.
(obviamente, en el código real, usaría un contador entero en lugar de tokens reales y puede optimizar cada paso de 5/8 almacenando marcas de tiempo)
Leyendo la pregunta nuevamente, si el límite de velocidad se restablece completamente cada 8 segundos, entonces aquí hay una modificación:
Comience con una marca de tiempo, last_send
hace mucho tiempo (por ejemplo, en la época). Además, comience con el mismo cubo de 5 fichas.
Aplica la regla cada 5/8 segundos.
Cada vez que envía un mensaje: Primero, verifique si hace last_send
≥ 8 segundos. Si es así, llene el cubo (configúrelo en 5 fichas). En segundo lugar, si hay tokens en el depósito, envíe el mensaje (de lo contrario, soltar / esperar / etc.). Tercero, listo last_send
para ahora.
Eso debería funcionar para ese escenario.
De hecho, he escrito un bot IRC usando una estrategia como esta (el primer enfoque). Está en Perl, no en Python, pero aquí hay un código para ilustrar:
La primera parte aquí maneja agregar tokens al cubo. Puede ver la optimización de agregar tokens en función del tiempo (de la segunda a la última línea) y luego la última línea sujeta el contenido del depósito al 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 es una estructura de datos que se pasa. Esto está dentro de un método que se ejecuta de manera rutinaria (calcula cuándo será la próxima vez que tenga algo que hacer y duerme tanto tiempo o hasta que obtenga tráfico de red). La siguiente parte del método maneja el envío. Es bastante complicado, porque los mensajes tienen prioridades asociadas con ellos.
# 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] = [];
Esa es la primera cola, que se ejecuta sin importar qué. Incluso si se mata nuestra conexión por inundación. Se usa para cosas extremadamente importantes, como responder al PING del servidor. A continuación, el resto de las colas:
# 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}});
}
}
}
Finalmente, el estado del depósito se guarda nuevamente en la estructura de datos $ conn (en realidad un poco más adelante en el método; primero calcula qué tan pronto tendrá más trabajo)
# Save status.
$conn->{fujiko_limit_bucket} = $bucket;
$conn->{fujiko_limit_lasttx} = $start_time;
Como puede ver, el código real de manejo de la cubeta es muy pequeño, aproximadamente cuatro líneas. El resto del código es manejo prioritario de colas. El bot tiene colas prioritarias para que, por ejemplo, alguien que chatea con él no pueda evitar que realice sus importantes tareas de patada / prohibición.