令牌桶的实现相当简单。
从一个包含 5 个令牌的存储桶开始。
每 5/8 秒:如果桶中的令牌少于 5 个,则添加一个。
每次要发送消息时:如果bucket中的token≥1个,取出一个token发送消息。否则,等待/丢弃消息/无论如何。
(显然,在实际代码中,您将使用整数计数器而不是真实令牌,并且您可以通过存储时间戳来优化每 5/8 秒的步骤)
再次阅读问题,如果速率限制每8秒完全重置一次,那么这里是一个修改:
从很久以前的时间戳last_send 开始(例如,在纪元)。另外,从相同的 5 令牌桶开始。
执行每 5/8 秒规则。
每次发送消息时:首先检查last_send是否≥8秒前。如果是这样,请填充存储桶(将其设置为 5 个令牌)。其次,如果桶中有令牌,则发送消息(否则,丢弃/等待/等)。第三,将last_send设置为现在。
这应该适用于那种情况。
我实际上已经使用这样的策略(第一种方法)编写了一个 IRC 机器人。它在 Perl 中,而不是 Python,但这里有一些代码来说明:
这里的第一部分处理向存储桶添加令牌。可以看到基于时间(倒数第二行)添加token的优化,然后最后一行将bucket内容钳制到最大值(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}});
}
}
}
最后,bucket 状态被保存回 $conn 数据结构(实际上在该方法中稍晚一点;它首先计算它多久会有更多工作)
# Save status.
$conn->{fujiko_limit_bucket} = $bucket;
$conn->{fujiko_limit_lasttx} = $start_time;
如您所见,实际的桶处理代码非常小——大约四行。其余代码是优先队列处理。该机器人具有优先级队列,因此与它聊天的人无法阻止它执行重要的踢/禁止职责。