【问题标题】:Redis Rate Limiter PatternRedis 速率限制器模式
【发布时间】:2021-05-16 23:28:49
【问题描述】:

我正在尝试使用“模式:速率限制器 1”下 https://redis.io/commands/incr 中指定的 Redis 速率限制模式。但是,如果我想跨多个服务器进行速率限制,我该如何扩展它。就像我在负载均衡器后面的 5 台服务器上部署了服务一样,我希望 5 台服务器上每个 api 密钥的总请求数不应超过 x/sec。根据我提到的redis模式,问题是如果我的速率限制器本身在多个服务器上运行,那么对两个不同的速率限制器服务器的两个不同请求可以同时执行“获取密钥”并读取相同的值,之前任何人都会更新它,这可能会允许更多请求。我该如何处理?我显然可以将 get 放在 MULTI 块中,但我认为它会使事情变得更慢。

【问题讨论】:

    标签: redis load-balancing distributed-computing rate-limiting


    【解决方案1】:

    INCR 以更新后的值回复。所以它既可以用作写入命令,也可以用作读取命令。

    FUNCTION LIMIT_API_CALL(ip)
    ts = CURRENT_UNIX_TIME()
    keyname = ip+":"+ts
    
    MULTI
        INCR(keyname)
        EXPIRE(keyname,10)
    EXEC
    
    current = RESPONSE_OF_INCR_WITHIN_MULTI
    IF current > 10 THEN
        ERROR "too many requests per second"
    ELSE
        PERFORM_API_CALL()
    END
    

    【讨论】:

    • 是的。我喜欢它。你在更新后进行了计数检查。
    【解决方案2】:

    您需要运行 LUA 脚本来检查速率限制并增加/减少/重置计数器。

    你可以在这里找到一个关于 Larval 框架的简单例子

    https://github.com/laravel/framework/blob/8.x/src/Illuminate/Redis/Limiters/DurationLimiter.php

     /**
         * Get the Lua script for acquiring a lock.
         *
         * KEYS[1] - The limiter name
         * ARGV[1] - Current time in microseconds
         * ARGV[2] - Current time in seconds
         * ARGV[3] - Duration of the bucket
         * ARGV[4] - Allowed number of tasks
         *
         * @return string
         */
        protected function luaScript()
        {
            return <<<'LUA'
    local function reset()
        redis.call('HMSET', KEYS[1], 'start', ARGV[2], 'end', ARGV[2] + ARGV[3], 'count', 1)
        return redis.call('EXPIRE', KEYS[1], ARGV[3] * 2)
    end
    if redis.call('EXISTS', KEYS[1]) == 0 then
        return {reset(), ARGV[2] + ARGV[3], ARGV[4] - 1}
    end
    if ARGV[1] >= redis.call('HGET', KEYS[1], 'start') and ARGV[1] <= redis.call('HGET', KEYS[1], 'end') then
        return {
            tonumber(redis.call('HINCRBY', KEYS[1], 'count', 1)) <= tonumber(ARGV[4]),
            redis.call('HGET', KEYS[1], 'end'),
            ARGV[4] - redis.call('HGET', KEYS[1], 'count')
        }
    end
    return {reset(), ARGV[2] + ARGV[3], ARGV[4] - 1}
    LUA;
        }
    

    【讨论】:

    • 这也是一种方法
    【解决方案3】:

    接受的答案不正确。它导致不准确的计数器值。让我们看一个下面的例子:

    5 个客户端对 Redis 执行 5 个并发请求。计数器的当前状态是10,而极限也是10。

    5 个并发请求会将计数器增加到 15,同时拒绝每个请求。相反,该值应保持 10 以反映“允许”客户端的正确次数。

    解决方案: 我们实际上需要将两个单独的原子操作组合成一个原子操作。这就是 LUA 脚本的用武之地。它只是对 Redis 本身的修改,它引入了另一个以原子方式“执行 get,然后执行 set”的代码路径。这样做是因为 Redis 是单线程的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-08-16
      • 2018-05-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-02-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多