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