当前位置: 首页 > 面试题库 >

使用EVAL,SCAN和DEL的Redis通配符删除脚本返回“非确定性命令后不允许写入命令”

邓声
2023-03-14
问题内容

因此,我正在寻求构建一个lua脚本,该脚本使用SCAN来基于模式查找键并删除它们(从原子上)。我首先准备了以下脚本

local keys = {};
local done = false;
local cursor = "0"
repeat
    local result = redis.call("SCAN", cursor, "match", ARGV[1], "count", ARGV[2])
    cursor = result[1];
    keys = result[2];
    for i, key in ipairs(keys) do
        redis.call("DEL", key);
    end
    if cursor == "0" then
        done = true;
    end
until done
return true;

这会吐出以下“ Err:@user_script:9:在非确定性命令之后写不允许的命令”,所以我对此进行了思考,并提出了以下脚本:

local all_keys = {};
local keys = {};
local done = false;
local cursor = "0"
repeat
    local result = redis.call("SCAN", cursor, "match", ARGV[1], "count", ARGV[2])
    cursor = result[1];
    keys = result[2];
    for i, key in ipairs(keys) do
        all_keys[#all_keys+1] = key;
    end
    if cursor == "0" then
        done = true;
    end
until done
for i, key in ipairs(all_keys) do
    redis.call("DEL", key);
end
return true;

仍然返回相同的错误(@user_script:17:在不确定性命令之后不允许写入命令)。这让我难过。有什么办法可以避免这个问题?

脚本使用phpredis和以下命令运行

$args_arr = [
          0 => 'test*',   //pattern
          1 => 100,     //count for SCAN
  ];
  var_dump($redis->eval($script, $args_arr, 0));

问题答案:

更新: 以下内容适用于3.2之前的Redis版本。从该版本开始,基于效果的复制取消了对非确定性的禁令,因此所有赌注都是关闭(或更确切地说是打开)。

您不能(也不应)将SCAN命令族与脚本中的任何写命令混合使用,因为前者的答复取决于内部Redis数据结构,而内部Redis数据结构又是服务器进程所独有的。换句话说,不能保证两个Redis进程(例如主进程和从属进程)返回相同的答复(因此,在Redis复制上下文中(不是基于操作,而是基于语句的)会破坏它)。

Redis的试图通过阻止任何写入命令(例如,以保护自身免受这样的情况下DEL),如果它是一个随机命令后执行(例如SCAN而且TIMESRANDMEMBER和类似的)。我敢肯定有解决这个问题的方法,但是您想这样做吗?请记住,您将进入未定义系统行为的未知区域。

取而代之的是,接受一个事实,即您不应该将随机的读写混合使用,并尝试考虑一种解决问题的不同方法,即按照原子方式根据模式删除一堆密钥。

首先问问自己是否可以放宽任何要求。它必须是原子的吗?原子性意味着Redis在删除期间将被阻止(无论最终实现如何),并且操作的时间取决于作业的大小(即,删除的键数及其内容[删除大的键比删除短字符串更昂贵]])。

如果不需要原子性,则定期/懒惰SCAN并小批量删除。如果必须的话,请理解您基本上是在尝试模仿
邪恶的KEYS命令:)但是,如果您事先了解该模式,则可以做得更好。

假设模式在您的应用程序运行时是已知的,那么您可以收集相关的密钥(例如,在Set中),然后使用该集合以原子安全且复制安全的方式实现删除,这比遍历整个密钥空间更有效。

但是,最“棘手”的问题是,您需要在确保原子性的同时运行临时模式匹配。如果是这样,则问题归结为立即获取键空间的按模式过滤的快照,然后立即进行一系列删除(重新强调:阻止数据库时)。在这种情况下,您可以很好地KEYS在Lua脚本中使用并希望获得最好的结果(但要知道,您可能SHUTDOWN NOSAVE很快就会诉诸:P)。

最后的优化是索引键空间本身。两者SCANKEYS基本上都是全表扫描,那么如果我们要索引该表怎么办?想象一下,在交易期间可以查询的键名称上保留索引-
您可能可以使用排序集和字典范围(HT @TwBert
)来消除大多数模式匹配需求。但是代价不菲……您不仅要进行双重记账(将每个键的名称成本存储在RAM和CPU中),而且还被迫增加应用程序的复杂性。为什么要增加复杂性?因为要实现这样的索引,您必须自己在应用程序层(以及可能的所有其他Lua脚本)中维护它,因此请在事务中仔细包装对Redis的每个写操作,该事务还会更新索引。

假设您做了所有这些工作(并考虑到了明显的陷阱,例如增加了复杂性的潜在错误,Redis,RAM和CPU的写入负载至少翻了一番,缩放限制等),您可以在肩并祝贺您使用非Redis设计的方式使用Redis。尽管即将发布的Redis版本可能(或可能没有)针对此挑战提供了更好的解决方案(
@TwBert-想要进行RCP / contrib联合并再次破解Redis?
),但在尝试此操作之前,我真的敦促您重新考虑原始要求并确认您正确使用了Redis(即根据数据访问需求设计“模式”)。



 类似资料:
  • 问题内容: Redis中命令的准确性如何? 我注意到,返回的键数与命令返回的实际键数不匹配。 这是一个例子: 为什么键数比实际数字高得多? 问题答案: 我想说这与密钥到期有关。 键/值存储(例如Redis或memcached)无法为每个要过期的对象定义物理计时器。他们太多了。取而代之的是,他们定义一个数据结构以轻松跟踪要过期的项目,并将所有过期事件多路复用到单个物理计时器。他们还倾向于实施惰性策略

  • 问题内容: 我正在尝试使用以下方式将数据加载到mysql数据库中 这个问题的主题是我得到的答复。我了解默认情况下本地数据卸载是关闭的,因此我必须使用命令启用它,但我不知道将此命令放置在何处。 问题答案: 您可以在设置客户端连接时将其指定为附加选项: 这是因为该功能打开了一个安全漏洞。因此,如果您确实想使用它,则必须以显式方式启用它。 客户端和服务器都应启用本地文件选项。否则它将不起作用。要为服务器

  • 问题内容: 在Redis中,我通过CLI运行Lua脚本,如下所示:- 因此,我的Lua脚本接受4个键和2个参数。 现在,我想在Node.js中运行相同的脚本。 我正在使用此库在我的应用程序中导入Redis。 我没有找到任何有关执行Lua脚本的函数参数的示例。 因此,我只是碰到一些可能起作用的东西。但是似乎没有任何作用。 我的app.js像这样: 我的问题:如何使用node.js执行以下命令,以便它

  • 问题内容: 使用redis时,出现错误: info命令显示: 用内存高吗?我是一个完整的Redis新手。如果是这样,怎么会出现此问题,我应该从这里继续进行吗?同一错误都在生产环境中出现(Heroku),因此,我们非常感谢您的帮助。谢谢。 问题答案: 达到最大内存限制时,将返回此消息。您可以使用以下命令检查当前限制: 结果以字节为单位。 请注意,一个空的Redis实例使用大约710KB的内存(在Li

  • 问题内容: 我有一个Bash shell脚本,可以调用许多命令。如果任何命令返回的非零值,我都希望shell脚本自动返回1的返回值。 如果不显式检查每个命令的结果,是否有可能? 例如 问题答案: 将此添加到脚本的开头: 如果一个简单的命令以非零退出值退出,这将导致shell立即退出。简单命令是不属于if,while或直到测试的一部分的任何命令,也不是&&或||的一部分的任何命令。清单。 有关更多详

  • 问题内容: 我删除了托管工作的旧詹金斯版本: 这些旧版本仍在作业页面中可见。如何使用命令行删除它们? (在每个构建用户界面中没有删除按钮) 问题答案: 似乎已将其添加到CLI中,或者至少正在使用它:http : //jenkins.361315.n4.nabble.com/How-to- purge-old-builds-td385290.html 语法如下所示: