分布式限流:基于Redis的有序集合

2021-11-28  本文已影响0人  Go语言由浅入深

本文作者是来自国外一家名为ClassDojo的科技公司,其分享了在企业构建推送通知服务的限流实践。该服务要求具备以下标准的限流功能:

我们查看了可用的限流器选择,但在NPM(node.js包管理,他们的服务应该是基于node.js开发)未找到符合上述要求的包。所以决定自己写,你可以在这里下载

方案一:token buckets(令牌桶)

使用滚动窗口限流的经典算法是令牌桶(或漏桶算法)。下面是它的工作原理:

这是一种非常聪明的算法,只占用很少的空间(每个用户只占用一个整数计数器),但是这种直接的实现有一个很大的缺陷——一些进程需要不断地填充桶。由于有数百万用户,而且每次填充操作都需要写操作,这对我们的Redis实例来说是一个不可持续的负载。这里有一个更高级的方法:

更好方法:sorted sets(有序集合)

幸运的是,Redis有另一个数据结构,我们可以用来防止这些竞争条件-有序集合。这是我们想出的算法:

然而,这种方法有一个瑕疵——我们不受影响,但可能不适合他人的要求。在我们的算法中,你可以看到一个操作是否被阻塞是在所有Redis操作完成后决定的。这意味着被阻止的操作仍然算作操作。所以,如果用户持续超过速率限制,他们的任何操作都将不被允许(在前几次之后),而不是允许偶尔的操作通过。

模块

我们在npm上开源了我们的限制器,作为rolling-rate-limiter模块。它可以使用Redis后端,或者,如果你不需要在多进程中运行你的限流器,在内存中操作(使用一个简单的数组而不是排序集)。这里有一个例子:

/*  Setup:  */

  var RateLimiter = require("rolling-rate-limiter");
  var Redis = require("redis");
  var client = Redis.createClient(config);

  var limiter = RateLimiter({
    redis: client,
    namespace: "UserLoginLimiter"
    interval: 1000
    maxInInterval: 10
    minDifference: 100
  });

  /*  Action:  */

  function attemptAction(userId, cb) {
    limiter(userId, function(err, timeLeft) {
      if (err) {

        // redis failed or similar.

      } else if (timeLeft > 0) {

        // limit was exceeded, action should not be allowed
        // timeLeft is the number of ms until the next action will be allowed
        // note that this can be treated as a boolean, since 0 is falsy

      } else {

        // limit was not exceeded, action should be allowed

      }
    });
  }

你也可以很容易地使用它与中间件结合对请求限速,如下所示:

var limiter = RateLimiter({
    redis: redisClient,
    namespace: "requestRateLimiter",
    interval: 60000,
    maxInInterval: 100,
    minDifference: 100
  });

  app.use(function(req, res, next) {

    // "req.ipAddress" could be replaced with any unique user identifier
    limiter(req.ipAddress, function(err, timeLeft) {
      if (err) {
        return res.status(500).send();
      } else if (timeLeft) {
        return res.status(429).send("You must wait " + timeLeft + " ms before you can make requests.");
      } else {
        return next();
      }
    });

  });

你可以在Github上查看源代码。我们希望这将有助于您编写Node.js应用程序!

总结

本文介绍了一种基于Redis有序集合实现的分布式限流器,该方法特别适合软件通知服务限流。当然每种算法都不是放之四海而皆准的,需要根据自己的场景来选择或者改造。虽然文章使用的是node.js实现,读者也可以尝试用Go来实践下。

上一篇下一篇

猜你喜欢

热点阅读