当前位置: 首页 > 知识库问答 >
问题:

如何使用Redis实现速率限制

林威
2023-03-14

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

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

if count > 5
    print "Exceeded the limit"    

然而,可以在最后一分钟发送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

共有3个答案

曾修真
2023-03-14

这是一个已经回答过的老问题,但是这里有一个实现,我从这里获得了一些灵感。我用ioredisode.js

这是滚动窗口时间限制器的所有异步但无种族条件(我希望)的荣耀:

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);
          }
        }
      });
    }
  });
};

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

// 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);
      }
    }
  });
};

这是功能的要点。如果你看到任何问题,请告诉我。

漆雕成弘
2023-03-14

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

查看这篇文章,了解确切的实现:使用Redis排序集实现更好的速率限制

更新:

还有另一种算法,与漏桶相比有一些优势。它被称为通用细胞率算法。以下是它在更高级别上的工作方式,如速率限制、单元格和GCRA中所述:

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

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

卫鸿朗
2023-03-14

您可以从"5请求在最后一分钟"切换到"5请求在分钟x"。这样就有可能做到:

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个请求”,那么您可以这样做

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"

如果您有生产限制(特别是性能限制),建议不要使用关键字。我们可以使用集合来代替:

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代码,但应该给你一个想法。

 类似资料:
  • 我试图在Spring云网关中设置速率限制。 我试图在应用程序中配置过滤器。yaml如下所示 我得到下面的错误 如何在spring云网关中实现速率限制?

  • 问题内容: 我将作为个人练习用Java实现一个(简单的)下载器应用程序。它将在不同的线程中运行多个作业,以这种方式,我将在执行期间始终同时下载几个文件。 我希望能够定义在所有下载作业之间共享的下载速率限制,但是我不知道如何执行单个下载任务。我应该怎么做呢?我应该尝试实施哪些解决方案? 谢谢。 问题答案: 我将从管理所有下载的DownloadManager开始。 希望参与托管带宽的所有代码都将在开始

  • 我想了解限速器是怎么工作的。 我在博客中看到的是: 到现在为止,我忽略了删除旧的钥匙。我不明白这个索伦是怎么工作的?正如上面所说,在多线程环境中,代码将在一秒钟内完成10个以上的api调用。它只能通过将redisclient.get(key)同步到callApi()代码来解决。 我从 假设在当前的第二个中已经服务了9个请求,现在同时出现了5个新的请求,所有这些新的线程在这5个线程中的任何一个执行“

  • 我需要限制每分钟的请求数。我还没有找到任何方法来使用Spring云网关redis。下面是当前的实现:图像实现 customRouter.getBurstCapacitycustomRouter.getReplenishRate 两者都仅在几秒钟内运行。示例:20 个请求在同一秒内与。 我在1分钟内需要20个请求。

  • 速率限制配置参考 filter.http.RateLimit filter.http.RateLimit proto { "domain": "...", "stage": "...", "request_type": "...", "timeout": "{...}" } domain (string, REQUIRED) 需要调用速率限制服务时的域。 stage (uint3

  • 速率限制配置参考。 filter.network.RateLimit filter.network.RateLimit proto { "stat_prefix": "...", "domain": "...", "descriptors": [], "timeout": "{...}" } stat_prefix (string, REQUIRED) 发布统计信息时使用的前缀。