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

Bil*_*lly 13 lua redis

所以我正在寻求构建一个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;
Run Code Online (Sandbox Code Playgroud)

哪个会吐回以下"错误:@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;
Run Code Online (Sandbox Code Playgroud)

仍然返回相同的错误(@user_script:17:非确定性命令后不允许写入命令).这让我很难过.有没有办法绕过这个问题?

脚本是使用phpredis和以下运行的

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

Ita*_*ber 13

更新:以下适用于最高3.2的Redis版本.从那个版本开始,基于效果的复制解除了对非决定论的禁令,所以所有的赌注都是关闭的(或者更确切地说是开启).

您不能(也不应该)将SCAN命令族与脚本中的任何写命令混合在一起,因为前者的回复依赖于内部Redis数据结构,而这些数据结构又是服务器进程唯一的.换句话说,两个Redis进程(例如主服务器和从服务器)不能保证返回相同的回复(因此在Redis复制上下文[这不是操作 - 但基于语句]会破坏它).

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

相反,接受这样一个事实:你不应该混合随机读写,并尝试考虑一种不同的方法来解决你的问题,即以原子方式根据模式删除一串键.

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

如果原子性不是必须的,定期/懒惰SCAN并且小批量删除.如果它是必须的,要明白你基本上试图模仿邪恶的KEYS命令:)但如果你事先了解这种模式,你可以做得更好.

假设在应用程序的运行时期间已知该模式,您可以收集相关的键(例如,在Set中),然后使用该集合以原子和复制安全的方式实现删除,与遍历整个键空间相比更高效.

但是,最"困难"的问题是,如果您需要在确保原子性的同时运行ad-hoc模式匹配.如果是这样,问题归结为获得密钥空间的按模式过滤的快照,紧接着是一系列删除(重新强调:当数据库被阻止时).在这种情况下,你可以很好地使用KEYS你的Lua脚本,并希望最好的...(但完全知道,你可以SHUTDOWN NOSAVE很快采取:P).

Last Optimization用于索引键空间本身.双方SCANKEYS基本上全表扫描,所以如果我们要索引的表?想象一下,在事务期间可以查询的键名称上保留索引 - 您可以使用排序集和词典范围(HT @TwBert)来消除大多数模式匹配需求.但是成本很高......你不仅会进行双重记账(将每个密钥的名称成本存储在RAM和CPU中),还会被迫增加应用程序的复杂性.为何增加复杂性?因为要实现这样的索引,您必须自己在应用程序层(可能还有所有其他Lua脚本)中进行维护,在每次更新索引的事务中将每个写入操作小心地包装到Redis中.

假设你做了所有这些(并考虑到明显的陷阱,例如增加的复杂性可能存在错误,至少加倍Redis,RAM和CPU上的写入负载,对扩展的限制等等),你可以拍拍自己肩并祝贺自己以不适合的方式使用Redis.虽然即将推出的Redis版本可能(或可能不)包含更好的解决方案来应对这一挑战(@TwBert - 想要联合RCP/contrib并再次破解Redis?),在尝试之前我真的恳请您重新考虑原始要求并验证您是否正确使用Redis(即根据您的数据访问需求设计"架构").