繁体   English   中英

redis键在-1处卡住TTL

[英]redis keys getting stuck with TTL at -1

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

我发现redis无法清除某些键并在-1处报告其TTL 这是一个使用占位符IP地址的redis-cli会话演示此示例:

> 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”调用之间存在并发问题,导致您所描述的确切问题。

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

首先,“ get”调用将返回当前限制。 假设它返回了500。然后在短短的几分之一毫秒内,该键的TTL失效,因此Redis中不再存在该键。 因此,代码继续运行,并且达到了“ decr”调用。 还可以在这里找到错误:

decr文档指出(我的重点):

将键处存储的数字减一。 如果键不存在,则在执行操作之前将其设置为0 (......)

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

解决方案可能是使用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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM