什么是好的速率限制算法?

时间:2022-08-27 11:03:32

I could use some pseudo-code, or better, Python. I am trying to implement a rate-limiting queue for a Python IRC bot, and it partially works, but if someone triggers less messages than the limit (e.g., rate limit is 5 messages per 8 seconds, and the person triggers only 4), and the next trigger is over the 8 seconds (e.g., 16 seconds later), the bot sends the message, but the queue becomes full and the bot waits 8 seconds, even though it's not needed since the 8 second period has lapsed.

我可以使用一些伪代码,或者更好的Python。我想实现一个Python IRC病原队列,这部分工作,但如果有人触发消息低于限制(例如,速率限制是5每8秒的消息,和触发只有4)的人,和下一个触发器是8秒(例如,16秒后),机器人发送信息,但是队列变得完整和机器人等8秒,即使它是不需要自8第二期失效。

10 个解决方案

#1


199  

Here the simplest algorithm, if you want just to drop messages when they arrive too quickly (instead of queuing them, which makes sense because the queue might get arbitrarily large):

这里最简单的算法,如果你想要在它们到达的太快时删除消息(而不是排队,这是有意义的,因为队列可能会变得任意大):

rate = 5.0; // unit: messages
per  = 8.0; // unit: seconds
allowance = rate; // unit: messages
last_check = now(); // floating-point, e.g. usec accuracy. Unit: seconds

when (message_received):
  current = now();
  time_passed = current - last_check;
  last_check = current;
  allowance += time_passed * (rate / per);
  if (allowance > rate):
    allowance = rate; // throttle
  if (allowance < 1.0):
    discard_message();
  else:
    forward_message();
    allowance -= 1.0;

There are no datastructures, timers etc. in this solution and it works cleanly :) To see this, 'allowance' grows at speed 5/8 units per seconds at most, i.e. at most five units per eight seconds. Every message that is forwarded deducts one unit, so you can't send more than five messages per every eight seconds.

在这个解决方案中没有数据结构、定时器等,它工作得很干净:)看到这个,‘零用’以每秒钟5/8单位的速度增长,即每8秒最多5个单位。每个转发的消息都要扣除一个单元,所以每8秒就不能发送超过5条消息。

Note that rate should be an integer, i.e. without non-zero decimal part, or the algorithm won't work correctly (actual rate will not be rate/per). E.g. rate=0.5; per=1.0; does not work because allowance will never grow to 1.0. But rate=1.0; per=2.0; works fine.

注意,速率应该是一个整数,即没有非零小数部分,或者算法不能正确工作(实际速率不是速率/per)。例如率= 0.5;/ = 1.0;因为零用钱永远不会增长到1.0。但率= 1.0;/ = 2.0;工作很好。

#2


41  

Use this decorator @RateLimited(ratepersec) before your function that enqueues.

在进行排队的函数之前使用这个decorator @RateLimited(ratepersec)。

Basically, this checks if 1/rate secs have passed since the last time and if not, waits the remainder of the time, otherwise it doesn't wait. This effectively limits you to rate/sec. The decorator can be applied to any function you want rate-limited.

基本上,它检查自上次以来1/rate secs是否已经通过,如果没有,则等待剩余的时间,否则它不会等待。这实际上限制了你评估/秒。decorator可以应用于任何您想要的限速函数。

In your case, if you want a maximum of 5 messages per 8 seconds, use @RateLimited(0.625) before your sendToQueue function.

在您的示例中,如果您希望每8秒最多有5条消息,请在sendToQueue函数之前使用@RateLimited(0.625)。

import time

def RateLimited(maxPerSecond):
    minInterval = 1.0 / float(maxPerSecond)
    def decorate(func):
        lastTimeCalled = [0.0]
        def rateLimitedFunction(*args,**kargs):
            elapsed = time.clock() - lastTimeCalled[0]
            leftToWait = minInterval - elapsed
            if leftToWait>0:
                time.sleep(leftToWait)
            ret = func(*args,**kargs)
            lastTimeCalled[0] = time.clock()
            return ret
        return rateLimitedFunction
    return decorate

@RateLimited(2)  # 2 per second at most
def PrintNumber(num):
    print num

if __name__ == "__main__":
    print "This should print 1,2,3... at about 2 per second."
    for i in range(1,100):
        PrintNumber(i)

#3


23  

A Token Bucket is fairly simple to implement.

实现令牌桶相当简单。

Start with a bucket with 5 tokens.

从一个有5个标记的桶开始。

Every 5/8 seconds: If the bucket has less than 5 tokens, add one.

每5/8秒:如果桶中有少于5个令牌,添加一个。

Each time you want to send a message: If the bucket has ≥1 token, take one token out and send the message. Otherwise, wait/drop the message/whatever.

每次你想传达一个信息:如果桶≥1令牌,令牌和一个发送消息。否则,等待/降/任何的消息。

(obviously, in actual code, you'd use an integer counter instead of real tokens and you can optimize out the every 5/8s step by storing timestamps)

(显然,在实际代码中,您将使用整数计数器而不是真正的令牌,您可以通过存储时间戳优化每5/8s步骤)


Reading the question again, if the rate limit is fully reset each 8 seconds, then here is a modification:

再读一遍问题,如果速率限制每8秒完全重置一次,那么这里有一个修改:

Start with a timestamp, last_send, at a time long ago (e.g., at the epoch). Also, start with the same 5-token bucket.

从一个时间戳,last_send,在很久以前(例如,在那个时代)开始。同样,从相同的5令牌桶开始。

Strike the every 5/8 seconds rule.

每5/8秒执行一次规则。

Each time you send a message: First, check if last_send ≥ 8 seconds ago. If so, fill the bucket (set it to 5 tokens). Second, if there are tokens in the bucket, send the message (otherwise, drop/wait/etc.). Third, set last_send to now.

每次你发送信息:首先,检查是否last_send≥8秒之前。如果是,填充桶(设置为5个令牌)。其次,如果桶中有令牌,则发送消息(否则,删除/等待/等等)。第三,设置last_send到now。

That should work for that scenario.

这应该适用于这种情况。


I've actually written an IRC bot using a strategy like this (the first approach). Its in Perl, not Python, but here is some code to illustrate:

我实际上已经用这样的策略编写了一个IRC机器人(第一个方法)。它使用的是Perl,而不是Python,但是这里有一些代码来说明:

The first part here handles adding tokens to the bucket. You can see the optimization of adding tokens based on time (2nd to last line) and then the last line clamps bucket contents to the maximum (MESSAGE_BURST)

这里的第一部分处理向bucket添加令牌。您可以看到根据时间(从第2行到最后一行)添加令牌的优化,然后最后一行将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 is a data structure which is passed around. This is inside a method that runs routinely (it calculates when the next time it'll have something to do, and sleeps either that long or until it gets network traffic). The next part of the method handles sending. It is rather complicated, because messages have priorities associated with them.

$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] = [];

That's the first queue, which is run no matter what. Even if it gets our connection killed for flooding. Used for extremely important things, like responding to the server's PING. Next, the rest of the queues:

这是第一个队列,无论如何都要运行。即使我们的连接因洪水而中断。用于非常重要的事情,如响应服务器的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}});
                    }
            }
    }

Finally, the bucket status is saved back to the $conn data structure (actually a bit later in the method; it first calculates how soon it'll have more work)

最后,桶状态被保存到$conn数据结构中(实际上在方法稍后一点;它首先计算出多快完成更多的工作)

    # Save status.
    $conn->{fujiko_limit_bucket} = $bucket;
    $conn->{fujiko_limit_lasttx} = $start_time;

As you can see, the actual bucket handling code is very small — about four lines. The rest of the code is priority queue handling. The bot has priority queues so that e.g., someone chatting with it can't prevent it from doing its important kick/ban duties.

如您所见,实际的桶处理代码非常小—大约有四行。其余的代码是优先级队列处理。这个机器人有优先级队列,因此,与它聊天的人不能阻止它执行重要的踢/禁止任务。

#4


9  

to block processing until the message can be sent, thus queuing up further messages, antti's beautiful solution may also be modified like this:

为了阻止处理,直到消息可以发送,从而排队等待更多的消息,antti的漂亮解决方案也可以这样修改:

rate = 5.0; // unit: messages
per  = 8.0; // unit: seconds
allowance = rate; // unit: messages
last_check = now(); // floating-point, e.g. usec accuracy. Unit: seconds

when (message_received):
  current = now();
  time_passed = current - last_check;
  last_check = current;
  allowance += time_passed * (rate / per);
  if (allowance > rate):
    allowance = rate; // throttle
  if (allowance < 1.0):
    time.sleep( (1-allowance) * (per/rate))
    forward_message();
    allowance = 0.0;
  else:
    forward_message();
    allowance -= 1.0;

it just waits until enough allowance is there to send the message. to not start with two times the rate, allowance may also initialized with 0.

它只是等待足够的零用钱来发送信息。为了不以两倍的速率开始,零用钱也可以初始化为0。

#5


2  

Keep the time that the last five lines were sent. Hold the queued messages until the time the fifth-most-recent message (if it exists) is a least 8 seconds in the past (with last_five as an array of times):

保留最后五条线发送的时间。保存排队的消息,直到最近的消息(如果存在的话)在过去至少8秒(last_five作为时间数组):

now = time.time()
if len(last_five) == 0 or (now - last_five[-1]) >= 8.0:
    last_five.insert(0, now)
    send_message(msg)
if len(last_five) > 5:
    last_five.pop()

#6


1  

One solution is to attach a timestamp to each queue item and to discard the item after 8 seconds have passed. You can perform this check each time the queue is added to.

一种解决方案是向每个队列项附加一个时间戳,并在8秒后丢弃该项。您可以在每次添加队列时执行此检查。

This only works if you limit the queue size to 5 and discard any additions whilst the queue is full.

这只适用于将队列大小限制为5并在队列满时丢弃任何添加的内容。

#7


1  

If someone still interested, I use this simple callable class in conjunction with a timed LRU key value storage to limit request rate per IP. Uses a deque, but can rewritten to be used with a list instead.

如果有人仍然感兴趣,我将这个简单的可调用类与一个定时的LRU键值存储结合使用,以限制每个IP的请求率。使用deque,但可以重写为与列表一起使用。

from collections import deque
import time


class RateLimiter:
    def __init__(self, maxRate=5, timeUnit=1):
        self.timeUnit = timeUnit
        self.deque = deque(maxlen=maxRate)

    def __call__(self):
        if self.deque.maxlen == len(self.deque):
            cTime = time.time()
            if cTime - self.deque[0] > self.timeUnit:
                self.deque.append(cTime)
                return False
            else:
                return True
        self.deque.append(time.time())
        return False

r = RateLimiter()
for i in range(0,100):
    time.sleep(0.1)
    print(i, "block" if r() else "pass")

#8


0  

How about this:

这个怎么样:

long check_time = System.currentTimeMillis();
int msgs_sent_count = 0;

private boolean isRateLimited(int msgs_per_sec) {
    if (System.currentTimeMillis() - check_time > 1000) {
        check_time = System.currentTimeMillis();
        msgs_sent_count = 0;
    }

    if (msgs_sent_count > (msgs_per_sec - 1)) {
        return true;
    } else {
        msgs_sent_count++;
    }

    return false;
}

#9


0  

I needed a variation in Scala. Here it is:

我需要Scala的变体。这里是:

case class Limiter[-A, +B](callsPerSecond: (Double, Double), f: A ⇒ B) extends (A ⇒ B) {

  import Thread.sleep
  private def now = System.currentTimeMillis / 1000.0
  private val (calls, sec) = callsPerSecond
  private var allowance  = 1.0
  private var last = now

  def apply(a: A): B = {
    synchronized {
      val t = now
      val delta_t = t - last
      last = t
      allowance += delta_t * (calls / sec)
      if (allowance > calls)
        allowance = calls
      if (allowance < 1d) {
        sleep(((1 - allowance) * (sec / calls) * 1000d).toLong)
      }
      allowance -= 1
    }
    f(a)
  }

}

Here is how it can be used:

以下是它的使用方法:

val f = Limiter((5d, 8d), { 
  _: Unit ⇒ 
    println(System.currentTimeMillis) 
})
while(true){f(())}

#10


0  

Just a python implementation of a code from accepted answer.

只是一个python实现的代码,从可接受的答案。

import time

class Object(object):
    pass

def get_throttler(rate, per):
    scope = Object()
    scope.allowance = rate
    scope.last_check = time.time()
    def throttler(fn):
        current = time.time()
        time_passed = current - scope.last_check;
        scope.last_check = current;
        scope.allowance = scope.allowance + time_passed * (rate / per)
        if (scope.allowance > rate):
          scope.allowance = rate
        if (scope.allowance < 1):
          pass
        else:
          fn()
          scope.allowance = scope.allowance - 1
    return throttler

#1


199  

Here the simplest algorithm, if you want just to drop messages when they arrive too quickly (instead of queuing them, which makes sense because the queue might get arbitrarily large):

这里最简单的算法,如果你想要在它们到达的太快时删除消息(而不是排队,这是有意义的,因为队列可能会变得任意大):

rate = 5.0; // unit: messages
per  = 8.0; // unit: seconds
allowance = rate; // unit: messages
last_check = now(); // floating-point, e.g. usec accuracy. Unit: seconds

when (message_received):
  current = now();
  time_passed = current - last_check;
  last_check = current;
  allowance += time_passed * (rate / per);
  if (allowance > rate):
    allowance = rate; // throttle
  if (allowance < 1.0):
    discard_message();
  else:
    forward_message();
    allowance -= 1.0;

There are no datastructures, timers etc. in this solution and it works cleanly :) To see this, 'allowance' grows at speed 5/8 units per seconds at most, i.e. at most five units per eight seconds. Every message that is forwarded deducts one unit, so you can't send more than five messages per every eight seconds.

在这个解决方案中没有数据结构、定时器等,它工作得很干净:)看到这个,‘零用’以每秒钟5/8单位的速度增长,即每8秒最多5个单位。每个转发的消息都要扣除一个单元,所以每8秒就不能发送超过5条消息。

Note that rate should be an integer, i.e. without non-zero decimal part, or the algorithm won't work correctly (actual rate will not be rate/per). E.g. rate=0.5; per=1.0; does not work because allowance will never grow to 1.0. But rate=1.0; per=2.0; works fine.

注意,速率应该是一个整数,即没有非零小数部分,或者算法不能正确工作(实际速率不是速率/per)。例如率= 0.5;/ = 1.0;因为零用钱永远不会增长到1.0。但率= 1.0;/ = 2.0;工作很好。

#2


41  

Use this decorator @RateLimited(ratepersec) before your function that enqueues.

在进行排队的函数之前使用这个decorator @RateLimited(ratepersec)。

Basically, this checks if 1/rate secs have passed since the last time and if not, waits the remainder of the time, otherwise it doesn't wait. This effectively limits you to rate/sec. The decorator can be applied to any function you want rate-limited.

基本上,它检查自上次以来1/rate secs是否已经通过,如果没有,则等待剩余的时间,否则它不会等待。这实际上限制了你评估/秒。decorator可以应用于任何您想要的限速函数。

In your case, if you want a maximum of 5 messages per 8 seconds, use @RateLimited(0.625) before your sendToQueue function.

在您的示例中,如果您希望每8秒最多有5条消息,请在sendToQueue函数之前使用@RateLimited(0.625)。

import time

def RateLimited(maxPerSecond):
    minInterval = 1.0 / float(maxPerSecond)
    def decorate(func):
        lastTimeCalled = [0.0]
        def rateLimitedFunction(*args,**kargs):
            elapsed = time.clock() - lastTimeCalled[0]
            leftToWait = minInterval - elapsed
            if leftToWait>0:
                time.sleep(leftToWait)
            ret = func(*args,**kargs)
            lastTimeCalled[0] = time.clock()
            return ret
        return rateLimitedFunction
    return decorate

@RateLimited(2)  # 2 per second at most
def PrintNumber(num):
    print num

if __name__ == "__main__":
    print "This should print 1,2,3... at about 2 per second."
    for i in range(1,100):
        PrintNumber(i)

#3


23  

A Token Bucket is fairly simple to implement.

实现令牌桶相当简单。

Start with a bucket with 5 tokens.

从一个有5个标记的桶开始。

Every 5/8 seconds: If the bucket has less than 5 tokens, add one.

每5/8秒:如果桶中有少于5个令牌,添加一个。

Each time you want to send a message: If the bucket has ≥1 token, take one token out and send the message. Otherwise, wait/drop the message/whatever.

每次你想传达一个信息:如果桶≥1令牌,令牌和一个发送消息。否则,等待/降/任何的消息。

(obviously, in actual code, you'd use an integer counter instead of real tokens and you can optimize out the every 5/8s step by storing timestamps)

(显然,在实际代码中,您将使用整数计数器而不是真正的令牌,您可以通过存储时间戳优化每5/8s步骤)


Reading the question again, if the rate limit is fully reset each 8 seconds, then here is a modification:

再读一遍问题,如果速率限制每8秒完全重置一次,那么这里有一个修改:

Start with a timestamp, last_send, at a time long ago (e.g., at the epoch). Also, start with the same 5-token bucket.

从一个时间戳,last_send,在很久以前(例如,在那个时代)开始。同样,从相同的5令牌桶开始。

Strike the every 5/8 seconds rule.

每5/8秒执行一次规则。

Each time you send a message: First, check if last_send ≥ 8 seconds ago. If so, fill the bucket (set it to 5 tokens). Second, if there are tokens in the bucket, send the message (otherwise, drop/wait/etc.). Third, set last_send to now.

每次你发送信息:首先,检查是否last_send≥8秒之前。如果是,填充桶(设置为5个令牌)。其次,如果桶中有令牌,则发送消息(否则,删除/等待/等等)。第三,设置last_send到now。

That should work for that scenario.

这应该适用于这种情况。


I've actually written an IRC bot using a strategy like this (the first approach). Its in Perl, not Python, but here is some code to illustrate:

我实际上已经用这样的策略编写了一个IRC机器人(第一个方法)。它使用的是Perl,而不是Python,但是这里有一些代码来说明:

The first part here handles adding tokens to the bucket. You can see the optimization of adding tokens based on time (2nd to last line) and then the last line clamps bucket contents to the maximum (MESSAGE_BURST)

这里的第一部分处理向bucket添加令牌。您可以看到根据时间(从第2行到最后一行)添加令牌的优化,然后最后一行将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 is a data structure which is passed around. This is inside a method that runs routinely (it calculates when the next time it'll have something to do, and sleeps either that long or until it gets network traffic). The next part of the method handles sending. It is rather complicated, because messages have priorities associated with them.

$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] = [];

That's the first queue, which is run no matter what. Even if it gets our connection killed for flooding. Used for extremely important things, like responding to the server's PING. Next, the rest of the queues:

这是第一个队列,无论如何都要运行。即使我们的连接因洪水而中断。用于非常重要的事情,如响应服务器的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}});
                    }
            }
    }

Finally, the bucket status is saved back to the $conn data structure (actually a bit later in the method; it first calculates how soon it'll have more work)

最后,桶状态被保存到$conn数据结构中(实际上在方法稍后一点;它首先计算出多快完成更多的工作)

    # Save status.
    $conn->{fujiko_limit_bucket} = $bucket;
    $conn->{fujiko_limit_lasttx} = $start_time;

As you can see, the actual bucket handling code is very small — about four lines. The rest of the code is priority queue handling. The bot has priority queues so that e.g., someone chatting with it can't prevent it from doing its important kick/ban duties.

如您所见,实际的桶处理代码非常小—大约有四行。其余的代码是优先级队列处理。这个机器人有优先级队列,因此,与它聊天的人不能阻止它执行重要的踢/禁止任务。

#4


9  

to block processing until the message can be sent, thus queuing up further messages, antti's beautiful solution may also be modified like this:

为了阻止处理,直到消息可以发送,从而排队等待更多的消息,antti的漂亮解决方案也可以这样修改:

rate = 5.0; // unit: messages
per  = 8.0; // unit: seconds
allowance = rate; // unit: messages
last_check = now(); // floating-point, e.g. usec accuracy. Unit: seconds

when (message_received):
  current = now();
  time_passed = current - last_check;
  last_check = current;
  allowance += time_passed * (rate / per);
  if (allowance > rate):
    allowance = rate; // throttle
  if (allowance < 1.0):
    time.sleep( (1-allowance) * (per/rate))
    forward_message();
    allowance = 0.0;
  else:
    forward_message();
    allowance -= 1.0;

it just waits until enough allowance is there to send the message. to not start with two times the rate, allowance may also initialized with 0.

它只是等待足够的零用钱来发送信息。为了不以两倍的速率开始,零用钱也可以初始化为0。

#5


2  

Keep the time that the last five lines were sent. Hold the queued messages until the time the fifth-most-recent message (if it exists) is a least 8 seconds in the past (with last_five as an array of times):

保留最后五条线发送的时间。保存排队的消息,直到最近的消息(如果存在的话)在过去至少8秒(last_five作为时间数组):

now = time.time()
if len(last_five) == 0 or (now - last_five[-1]) >= 8.0:
    last_five.insert(0, now)
    send_message(msg)
if len(last_five) > 5:
    last_five.pop()

#6


1  

One solution is to attach a timestamp to each queue item and to discard the item after 8 seconds have passed. You can perform this check each time the queue is added to.

一种解决方案是向每个队列项附加一个时间戳,并在8秒后丢弃该项。您可以在每次添加队列时执行此检查。

This only works if you limit the queue size to 5 and discard any additions whilst the queue is full.

这只适用于将队列大小限制为5并在队列满时丢弃任何添加的内容。

#7


1  

If someone still interested, I use this simple callable class in conjunction with a timed LRU key value storage to limit request rate per IP. Uses a deque, but can rewritten to be used with a list instead.

如果有人仍然感兴趣,我将这个简单的可调用类与一个定时的LRU键值存储结合使用,以限制每个IP的请求率。使用deque,但可以重写为与列表一起使用。

from collections import deque
import time


class RateLimiter:
    def __init__(self, maxRate=5, timeUnit=1):
        self.timeUnit = timeUnit
        self.deque = deque(maxlen=maxRate)

    def __call__(self):
        if self.deque.maxlen == len(self.deque):
            cTime = time.time()
            if cTime - self.deque[0] > self.timeUnit:
                self.deque.append(cTime)
                return False
            else:
                return True
        self.deque.append(time.time())
        return False

r = RateLimiter()
for i in range(0,100):
    time.sleep(0.1)
    print(i, "block" if r() else "pass")

#8


0  

How about this:

这个怎么样:

long check_time = System.currentTimeMillis();
int msgs_sent_count = 0;

private boolean isRateLimited(int msgs_per_sec) {
    if (System.currentTimeMillis() - check_time > 1000) {
        check_time = System.currentTimeMillis();
        msgs_sent_count = 0;
    }

    if (msgs_sent_count > (msgs_per_sec - 1)) {
        return true;
    } else {
        msgs_sent_count++;
    }

    return false;
}

#9


0  

I needed a variation in Scala. Here it is:

我需要Scala的变体。这里是:

case class Limiter[-A, +B](callsPerSecond: (Double, Double), f: A ⇒ B) extends (A ⇒ B) {

  import Thread.sleep
  private def now = System.currentTimeMillis / 1000.0
  private val (calls, sec) = callsPerSecond
  private var allowance  = 1.0
  private var last = now

  def apply(a: A): B = {
    synchronized {
      val t = now
      val delta_t = t - last
      last = t
      allowance += delta_t * (calls / sec)
      if (allowance > calls)
        allowance = calls
      if (allowance < 1d) {
        sleep(((1 - allowance) * (sec / calls) * 1000d).toLong)
      }
      allowance -= 1
    }
    f(a)
  }

}

Here is how it can be used:

以下是它的使用方法:

val f = Limiter((5d, 8d), { 
  _: Unit ⇒ 
    println(System.currentTimeMillis) 
})
while(true){f(())}

#10


0  

Just a python implementation of a code from accepted answer.

只是一个python实现的代码,从可接受的答案。

import time

class Object(object):
    pass

def get_throttler(rate, per):
    scope = Object()
    scope.allowance = rate
    scope.last_check = time.time()
    def throttler(fn):
        current = time.time()
        time_passed = current - scope.last_check;
        scope.last_check = current;
        scope.allowance = scope.allowance + time_passed * (rate / per)
        if (scope.allowance > rate):
          scope.allowance = rate
        if (scope.allowance < 1):
          pass
        else:
          fn()
          scope.allowance = scope.allowance - 1
    return throttler