首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >如何使用Redis实现速率限制

如何使用Redis实现速率限制
EN

Stack Overflow用户
提问于 2012-11-01 18:22:49
回答 9查看 20.1K关注 0票数 22

我使用INCREXPIRE来实现速率限制,例如每分钟5个请求:

代码语言:javascript
运行
复制
if EXISTS counter
    count = INCR counter
else
    EXPIRE counter 60
    count = INCR counter

if count > 5
    print "Exceeded the limit"    

但是,可以在最后一秒发送5个请求1分钟,并在2分钟的第一秒发送5个请求,即在2秒内发送10个请求。

如何避免这个问题?

更新:我想出了这个列表实现。这是一个好方法吗?

代码语言:javascript
运行
复制
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
EN

回答 9

Stack Overflow用户

回答已采纳

发布于 2012-11-01 19:35:52

你可以从“最后一分钟内5个请求”切换到“x分钟内5个请求”。通过这种方式,可以做到:

代码语言:javascript
运行
复制
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"

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

代码语言:javascript
运行
复制
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"

如果您有生产约束(尤其是性能约束),那么使用KEYS关键字就是not advised。我们可以使用集合来代替:

代码语言:javascript
运行
复制
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"

这些都是伪Ruby代码,但应该会给您一些启发。

票数 13
EN

Stack Overflow用户

发布于 2016-09-11 01:28:59

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

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

更新:

还有另一种算法,与漏桶相比有一些优势。它被称为Generic Cell Rate Algorithm。下面是它在更高级别上的工作方式,如Rate Limiting, Cells, and GCRA中所述

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

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

票数 7
EN

Stack Overflow用户

发布于 2016-02-11 16:22:39

这是一个已经回答过的老问题,但这里有一个我从这里得到一些灵感的实现。我正在为Node.js使用ioredis

以下是滚动窗口时间限制器的所有异步但无竞争条件(我希望)的荣誉:

代码语言:javascript
运行
复制
var Ioredis = require('ioredis');
var redis = new Ioredis();

// Rolling window rate limiter
//
// key is a unique identifier for the process or function call being limited
// exp is the expiry in milliseconds
// maxnum is the number of function calls allowed before expiry
var redis_limiter_rolling = function(key, maxnum, exp, next) {
  redis.multi([
    ['incr', 'limiter:num:' + key],
    ['time']
  ]).exec(function(err, results) {
    if (err) {
      next(err);
    } else {
      // unique incremented list number for this key
      var listnum = results[0][1];
      // current time
      var tcur = (parseInt(results[1][1][0], 10) * 1000) + Math.floor(parseInt(results[1][1][1], 10) / 1000);
      // absolute time of expiry
      var texpiry = tcur - exp;
      // get number of transacation in the last expiry time
      var listkey = 'limiter:list:' + key;
      redis.multi([
        ['zadd', listkey, tcur.toString(), listnum],
        ['zremrangebyscore', listkey, '-inf', texpiry.toString()],
        ['zcard', listkey]
      ]).exec(function(err, results) {
        if (err) {
          next(err);
        } else {
          // num is the number of calls in the last expiry time window
          var num = parseInt(results[2][1], 10);
          if (num <= maxnum) {
            // does not reach limit
            next(null, false, num, exp);
          } else {
            // limit surpassed
            next(null, true, num, exp);
          }
        }
      });
    }
  });
};

这是一种锁定式的速率限制器:

代码语言:javascript
运行
复制
// Lockout window rate limiter
//
// key is a unique identifier for the process or function call being limited
// exp is the expiry in milliseconds
// maxnum is the number of function calls allowed within expiry time
var util_limiter_lockout = function(key, maxnum, exp, next) {
  // lockout rate limiter
  var idkey = 'limiter:lock:' + key;
  redis.incr(idkey, function(err, result) {
    if (err) {
      next(err);
    } else {
      if (result <= maxnum) {
        // still within number of allowable calls
        // - reset expiry and allow next function call
        redis.expire(idkey, exp, function(err) {
          if (err) {
            next(err);
          } else {
            next(null, false, result);
          }
        });
      } else {
        // too many calls, user must wait for expiry of idkey
        next(null, true, result);
      }
    }
  });
};

Here's a gist of the functions。如果您看到任何问题,请告诉我。

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/13175050

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档