如何使用Redis实现速率限制

lui*_*uin 20 rate-limiting redis

我使用INCREXPIRE实现速率限制(对于下面的示例,每分钟只允许5个请求):

if EXISTS counter
    count = INCR counter
else
    EXPIRE counter 60
    count = INCR counter

if count > 5
    print "Exceeded the limit"    
Run Code Online (Sandbox Code Playgroud)

但是存在一个问题,即人们可以在最后一秒发送5个请求,在下一分钟发送5个其他请求,换句话说,在两秒钟内发出10个请求.

有没有更好的方法来避免这个问题?


更新:我刚才提出了一个想法:使用列表来实现它.

times = LLEN counter
if times < 5
    LPUSH counter now()
else
    time = LINDEX counter -1
    if now() - time < 60
        print "Exceeded the limit"
    else
        LPUSH counter now()
LTRIM counter 5
Run Code Online (Sandbox Code Playgroud)

这是一个好方法吗?

alt*_*lto 12

您可以从"最后一分钟的5个请求"切换到"分钟x的5个请求".通过这种方式可以做到:

counter = current_time # for example 15:03
count = INCR counter
EXPIRE counter 60 # just to make sure redis doesn't store it forever

if count > 5
  print "Exceeded the limit"
Run Code Online (Sandbox Code Playgroud)

如果你想继续使用"最后一分钟的5个请求",那么你可以这样做

counter = Time.now.to_i # this is Ruby and it returns the number of milliseconds since 1/1/1970
key = "counter:" + counter
INCR key
EXPIRE key 60

number_of_requests = KEYS "counter"*"
if number_of_requests > 5
  print "Exceeded the limit"
Run Code Online (Sandbox Code Playgroud)

如果您有生产限制(尤其是性能),则不建议使用KEYS关键字.我们可以改用套装:

counter = Time.now.to_i # this is Ruby and it returns the number of milliseconds since 1/1/1970
set = "my_set"
SADD set counter 1

members = SMEMBERS set

# remove all set members which are older than 1 minute
members {|member| SREM member if member[key] < (Time.now.to_i - 60000) }

if (SMEMBERS set).size > 5
  print "Exceeded the limit"
Run Code Online (Sandbox Code Playgroud)

这是所有伪Ruby代码,但应该给你一个想法.

  • 使用`keys`命令[不建议](http://redis.io/commands/keys). (5认同)

Ale*_*kov 6

进行速率限制的规范方法是通过漏桶算法。使用计数器的缺点是用户可以在计数器重置后立即执行一堆请求,即在您的情况下的下一分钟的第一秒内执行 5 个操作。漏桶算法解决了这个问题。简而言之,您可以使用有序集合来存储您的“漏桶”,使用操作时间戳作为填充它的键。

查看这篇文章以了解确切的实现: Better Rate Limiting With Redis Sorted Sets

更新:

还有另一种算法,它与漏桶相比有一些优势。它被称为通用信元速率算法。以下是它在更高级别的工作方式,如Rate Limiting、Cells 和 GCRA 中所述

GCRA 的工作原理是通过称为“理论到达时间”(TAT) 的时间来跟踪剩余限制,该时间通过将表示其成本的持续时间添加到当前时间来在第一个请求中播种。成本计算为我们的“排放间隔”(T)的乘数,它来自我们希望桶重新装满的速率。当任何后续请求进来时,我们采用现有的 TAT,从中减去代表限制总突发容量的固定缓冲区 (? + T),并将结果与​​当前时间进行比较。这个结果代表下一次允许请求。如果是过去,我们允许传入请求,如果是未来,我们不允许。请求成功后,通过添加 T 计算新的 TAT。

GitHub 上有一个实现此算法的 redis 模块:https : //github.com/brandur/redis-cell