所以我正在寻求构建一个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试图通过阻止任何写命令来保护自己免受此类情况的影响(例如 DEL
如果它是在随机命令之后执行的(例如, SCAN
但也 TIME
, SRANDMEMBER
和类似的)。我确定有办法解决这个问题,但你想这样做吗?请记住,您将进入未定义系统行为的未知区域。
相反,接受这样一个事实:你不应该混合随机读写,并尝试考虑一种不同的方法来解决你的问题,即以原子方式根据模式删除一堆键。
首先问问自己是否可以放松任何要求。它必须是原子的吗?原子性意味着Redis将在删除期间被阻止(无论最终实现),并且操作的长度取决于作业的大小(即删除的键的数量及其内容[删除大的集合是比删除短字符串更昂贵,例如])。
如果原子性不是必须的,定期/懒惰 SCAN
并小批量删除。如果这是必须的,要明白你基本上是在试图效仿 邪恶 KEYS
命令:)但如果您事先了解该模式,您可以做得更好。
假设在应用程序的运行时期间已知模式,您可以收集相关的键(例如,在Set中),然后使用该集合以原子和复制安全的方式实现删除,与遍历整个键空间相比更有效。
但是,最困难的问题是如果您需要在确保原子性的同时运行ad-hoc模式匹配。如果是这样,问题归结为获得密钥空间的按模式过滤的快照,紧接着是一系列删除(重新强调:数据库被阻止时)。在这种情况下,你可以很好地使用 KEYS
在你的Lua剧本中,并希望最好的...(但完全了解你可以诉诸 SHUTDOWN NOSAVE
很快:P)。
Last Optimization用于索引键空间本身。都 SCAN
和 KEYS
基本上都是全表扫描,那么如果我们要对该表进行索引呢?想象一下,在事务中可以查询的键名称上保留一个索引 - 你可以使用一个排序集和词典范围(HT) @TwBert)消除大多数模式匹配需求。但是成本很高......你不仅会进行双重记账(将每个密钥的名称成本存储在RAM和CPU中),还会被迫增加应用程序的复杂性。为何增加复杂性?因为要实现这样的索引,您必须自己在应用程序层(可能还有所有其他Lua脚本)中维护它,在每次更新索引的事务中小心地将每个写入操作包装到Redis。
假设你做了所有这些(并考虑到明显的陷阱,比如增加了复杂性的漏洞潜力,至少加倍Redis,RAM和CPU上的写入负载,缩放等限制......)你可以拍拍自己肩并祝贺自己以不适合的方式使用Redis。虽然即将推出的Redis版本可能(或可能不)包含更好的解决方案来应对这一挑战(@TwBert - 想要联合RCP / contrib并再次破解Redis一点?),在尝试此操作之前,我真的建议您重新考虑原始要求并验证您是否正确使用Redis(即根据您的数据访问需求设计“架构”)。