草庐IT

Redis 键卡在 -1 的 TTL

coder 2023-07-18 原文

我正在使用 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/

有关Redis 键卡在 -1 的 TTL的更多相关文章

  1. javascript - 使用哈希 url 卡在 Angular ui-router State.go 上? - 2

    我正在研究ui-router。我有一个状态:.state('new-personal-orders',{url:'/orders/new-personal-orders/:catId?',template:''})在我的Controller中,我可以使用$state.go('new-personal-orders',null,{reload:true})在Html文件中我有一个anchor标记:Link如果标签被点击,状态就会改变,'new-personal-orders'变成当前状态,在url中有尾随散列。然后url看起来像:http://localhost:3000/orders/

  2. javascript - 使用 Promises 时卡在变量上的最佳实践 - 2

    这个问题在这里已经有了答案:HowdoIaccesspreviouspromiseresultsina.then()chain?(17个答案)关闭7年前。我是Promises的新手,我想知道在链中向下移动时保持变量的最佳实践是什么?通过Promise连接到MongoDB非常简单:connectToMongoDB(data).done(function(db){varcollection=db.collection('test_inserts');//domorestuffhere});但是如果我必须连接到两个不同的数据库会怎样呢?connectToMongoDB1(data1).the

  3. javascript - 谷歌授权弹出窗口卡在 chrome 扩展中 - 2

    我正在使用gapi客户端在chrome扩展中访问GoogleDrive。第一步是授权我的应用程序。我正在使用gapi.auth.authorize来启动授权。在我通过gapi启动的弹出窗口授权应用程序后,窗口永远不会关闭并卡住,如下图所示。但是在后台授权成功了,因为如果我手动关闭窗口,下次我就看不到卡住的弹出窗口了。有人可以指出我做错了什么吗?在授权弹出窗口中点击“允许访问”后,然后显示卡住的空白弹出窗口我正在使用的代码functionhandleClientLoad(){gapi.client.setApiKey('MyAPIkey');window.setTimeout(check

  4. javascript - 附加到卸载事件时,sendBeacon 请求卡在 "(pending)" - 2

    当我直接在控制台中调用navigator.sendBeacon时,我立即在Chrome开发工具网络Pane中看到请求已成功完成。当我使用类似下面的代码将sendBeacon附加到beforeunload事件,然后离开页面时,我看到网络Pane中添加了一行,但其状态停留在“(待定)”并且从未发送。我做错了什么?window.addEventListener('beforeunload',function(){navigator.sendBeacon('https://www.example.com/sendBeacon-data-collector','Sentbyabeacon!');

  5. javascript - Node.js Express 静态服务器卡在图像上 - 仅限 Chrome - 2

    这是一个非常奇怪的错误,困扰了我很长时间。我有一个使用ExpressStatic中间件以及呈现Jade的单独路由的基本网站。这是我的配置app.set('views',__dirname+'/views');app.set('viewengine','jade');app.use(stylus.middleware({src:__dirname+'/public',dest:__dirname+'/public',compile:function(str,path){returnstylus(str).set('filename',path).set('compress',true).

  6. javascript - 单击后悬停状态卡在按钮上 - 2

    我有一个具有两种状态的表单:编辑和可见。当您单击一个图标来编辑表单时,底部的两个按钮(类似于提交)会出现以保存或取消。当我单击它们时,表单会更新(或取消)并且按钮会消失。问题是当我重新打开表单进行编辑(并且按钮再次可见)时,最后一次单击仍然在Chrome中应用了悬停状态。SaveCancel为简单起见,这里只是取消函数...$scope.cancel=function(){//setaflagforangulartohide/showeditingmodeinHTML$scope.editMode=false;}; 最佳答案 如之前

  7. Redis序列化和java存入Redis数据序列化反序列化总结 - 2

    背景:最近考虑java代码数据在保存redis时,通常要配置序列化,才能保存到redis中,然而我们知道Redis中也有序列化(RDB和AoF两种形式),有点混淆总结一下。java中数据保存redis过程序列化的原因是什么?解释:java虚拟机内存和redis内存是两块独立的内存空间,分属于两个不同的进程,不同的两个应用,在网络传输层表现为数据传输是用TCP二进制流进行传输的序列化最终的目的是为了对象可以跨平台存储,和进行网络传输。 而跨平台存储和网络传输的方式就是IO,而我们的IO支持的数据格式就是字节数组。java中如何序列化?packagecom.gisquest.cloud.oauth

  8. javascript - React、Redux、React-Router - 卡在安装 material-ui 时 - 2

    教程:但是由于我使用的是redux和react路由器,所以我无法真正将MuiThemeProvider放在链的顶部。包含此库的最佳方式是什么?这是我的ReactDOM.render函数:ReactDOM.render({devTools},document.getElementById('root'));这就是路由器:exportdefault(onLogout)=>(); 最佳答案 我是这样做的。我有一个index.js,我的package.json将其作为要启动的主文件(使用npmstart)。我在其中(为简洁起见删除了一些导入

  9. javascript - node.js + socket.io + redis + rails — 实时应用程序 - 2

    我需要向我的应用程序(RubyOnRails)添加实时性,因此,我认为更好的方法是使用node.js+socket.io+redis。我在后端(node.js)中有这个application.js文件varapp=require('http').createServer();vario=require('socket.io');varredis=require('redis').createClient();var_=require('underscore')._;io=io.listen(app);io.configure(function(){io.set("transports"

  10. javascript - Chrome 53 中的 `Allocation stack` 选项卡在哪里 - 2

    我正在阅读thisarticle关于内存分析。其中一个屏幕截图显示了Allocationstack选项卡:在我的53版Chrome中没有这样的标签:它去哪儿了?如何在Chrome53中找到Allocationstack中显示的信息? 最佳答案 要使用此功能,您需要转到DevTools->Settings并启用Recordheapallocationstacktraces。 关于javascript-Chrome53中的`Allocationstack`选项卡在哪里,我们在StackOve

随机推荐