Redis 限速模式

问题描述

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

解决方法

您需要运行 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',ARGV[3] * 2)
end
if redis.call('EXISTS',KEYS[1]) == 0 then
    return {reset(),ARGV[4] - 1}
end
if ARGV[1] >= redis.call('HGET','start') and ARGV[1] <= redis.call('HGET','end') then
    return {
        tonumber(redis.call('HINCRBY',1)) <= tonumber(ARGV[4]),redis.call('HGET','end'),ARGV[4] - redis.call('HGET','count')
    }
end
return {reset(),ARGV[4] - 1}
LUA;
    }
,

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