gpt4 book ai didi

Redis 键卡在 -1 的 TTL

转载 作者:IT王子 更新时间:2023-10-29 06:07:31 25 4
gpt4 key购买 nike

我正在使用 Redis 管理 API 的速率限制,并使用 SETEX 每小时自动重置速率限制。

我发现 Redis 无法清除某些键并在 -1 报告它们的 TTL。下面是使用占位符 IP 地址演示这一点的 redis-cli session 示例:

> GET allowance:127.0.0.1
> 0
> TTL allowance:127.0.0.1
-1
> GET allowance:127.0.0.1
0

请注意,尽管它的 TTL 为负,但当我 GET 它时,redis 不会清除它。

我已尝试重现此状态,但无法重现。

> SETEX doomedkey -1 hello
(error) ERR invalid expire time in SETEX
> SETEX doomedkey 0 hello
(error) ERR invalid expire time in SETEX
> SETEX doomedkey 5 hello
OK
> TTL doomedkey
4
> GET doomedkey
hello

(... wait 5 seconds)

> TTL doomedkey
-2
> GET doomedkey
(nil)

这是导致 redis 无法使这些键过期的某种不幸的竞争条件吗?在已成功过期的数万个中,只有大约 10 个仍停留在 -1 状态。

我正在使用 redis_version:2.8.9

最佳答案

我遇到了同样的问题,只使用 Redis 2.8.24,但也用它来限制 API 速率。

我怀疑您正在像这样进行速率限制(仅使用 Ruby 代码作为示例):

def consume_rate_limit
# Fetch the current limit for a given account or user
rate_limit = Redis.get('available_limit:account_id')

# It can be nil if not already initialized or if TTL has expired
if rate_limit == nil
# So let's just initialize it to the initial limit
# Let's use a window of 10,000 requests, resetting every hour
rate_limit = 10000
Redis.setex('available_limit:account_id', 3600, rate_limit - 1)
else
# If the key already exists, just decrement the limit
Redis.decr('available_limit:account_id')
end

# Return true if we are OK or false the limit has been reached
return (rate_limit > 0)
end

好吧,我在使用这种方法时发现“get”和“decr”调用之间存在并发问题,这导致了您所描述的确切问题。

当速率限制 key 的 TTL 在“get”调用之后但在“decr”调用之前到期时,就会发生此问题。会发生什么:

首先,“get”调用将返回当前限制。假设它返回了 500。然后在几分之一毫秒内,该 key 的 TTL 到期,因此它不再存在于 Redis 中。因此代码继续运行并到达“decr”调用。错误也出现在这里:

decr documentation状态(我的重点):

Decrements the number stored at key by one. If the key does not exist, it is set to 0 before performing the operation. (...)

由于键已经被删除(因为它已经过期),“decr”指令会将键初始化为零,然后递减,这就是键值为-1的原因。并且 key 将在没有 TTL 的情况下创建,因此发出 TTL key_name 也会发出 -1。

解决方案可能是将所有代码包装在一个 transaction block 中使用 MULTI 和 EXEC 命令。但是,这可能会很慢,因为它需要多次往返于 Redis 服务器。

我使用的解决方案是编写一个 Lua 脚本并使用 EVAL 命令运行它。它具有原子性的优势(这意味着没有并发问题)并且只有一个到 Redis 服务器的 RTT。

local expire_time = ARGV[1]
local initial_rate_limit = ARGV[2]
local rate_limit = redis.call('get', KEYS[1])
-- rate_limit will be false when the key does not exist.
-- That's because redis converts Nil to false in Lua scripts.
if rate_limit == false then
rate_limit = initial_rate_limit
redis.call('setex', KEYS[1], initial_rate_limit, rate_limit - 1)
else
redis.call('decr', KEYS[1])
end
return rate_limit

要使用它,我们可以将 consume_rate_limit 函数重写为:

def consume_rate_limit
script = <<-LUA
... that script above, omitting it here not to bloat things ...
LUA
rate_limit = Redis.eval(script, keys: ['available_limit:account_id'], argv: [3600, 10000]).to_i
return (rate_limit > 0)
end

关于Redis 键卡在 -1 的 TTL,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42350277/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com