我正在设置一个Redis实例,该实例接收由键/值对组成的数据点,该键可以具有不同类型的前缀。每个键都有一个ttl过期。每隔15分钟,我就计划运行一个lua脚本,它将对带有特定前缀的各种键的值进行统计。这是从运行节点脚本的cron作业中删除的。
local data = redis.call('KEYS', KEYS[1] .. "*")
for i=1,#data do
local value = 'rollup.' .. KEYS[1] .. redis.call('GET', data[i])
if redis.call('EXISTS', value)==1 then
redis.call('INCR', value)
else
redis.call('SET', value, 1)
end
end节点脚本:
cron.scheduleJob("*/15 * * * *", async () => {
try {
await redisServer.eval(fs.readFileSync("./dist/tally.lua"), 0);
} catch (ex) {
logger.error("Calling rollup lua failed. " + util.inspect(ex));
}
});我好像收到了这些错误
ReplyError: BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.周期性的。这是lua脚本的问题,还是有更好的方法可以运行?也就是说,除了在接收其他常规调用(如set、external和ping )的端口上运行外部lua脚本之外(我还有一个心跳,它每秒钟都会刷新,以确保它是实时的)。我读过Redis有一种安排任务的方法,但是我很难找到这样做的例子。或者也许有一种内置的方式来实现我想要做的事情?
编辑:我的基本问题可能是lua脚本正在原子地运行,即在发生这种情况时,其他任何东西都无法运行,从而导致与Redis的所有其他交互都备份起来。是否有更快的方法来完成一个计数,可能不涉及一个lua脚本,我应该提取数据并在节点脚本中进行计数吗?
发布于 2022-07-29 18:53:44
键的使用是一种反模式。
在redis中的任何地方使用Keys命令都被认为是一种真正的反模式,Keys在遍历整个键空间时实际上会阻塞Redis,这需要相当长的时间。
它扼杀了你的剧本
这里发生的是,您的lua脚本超过lua时间限制(默认为5秒),这是redis开始拒绝命令之前的最大允许时间,这是因为阻止lua脚本。
基本上,您运行的是一个糟糕的脚本,它阻止Redis做任何事情。
使用排序集代替
这似乎是最好用排序集来处理的东西。当然,您可以将TTLs保留在您想要过期的密钥上,这很好,但实际上,您可以将TTLs设置为排序集,并将过期时间设为记分,成员作为密钥名。
所以如果你做了一个:
SET foo bar EX 50从现在开始,您只需计算50秒的UNIX时间戳,并将其用作得分,假设为1659120294,然后将其添加到您的rollup zset:
ZADD zset:rollup 1659120294 foo然后,当您查询计数时所需要做的就是使用当前时间的计数,假设发生在1659120394:
ZCOUNT zset:rollup 1659120394 inf,它将显示所有未过期密钥的计数。
最棒的是,当您完成计数时,您可以一次进入并删除排序集中的所有过期密钥,使用ZREMRANGEBYSCORE,假设相同的时间。
ZREMRANGEBYSCORE zsetrollup -inf 1659120394ZADD和ZCOUNT非常快,它们分别是O(1)和O(log(n)),ZREMRANGE在每次从其中清除的元素数中的O(N)比O(N)慢一些。但是,与每隔15分钟对数据库运行密钥相比,这将是相对较小的。
发布于 2022-07-29 19:22:31
我决定在插入带有ttl的键/值对时,增加计数,而不是使用脚本主动记录数据,订阅键的过期,然后在该键到期时将其减少。加上这个答案,以防有人试图做类似的事情。
https://stackoverflow.com/questions/73168938
复制相似问题