因此,我正在寻求构建一个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复制上下文中(不是基于操作,而是基于语句的)会破坏它)。
SCAN
Redis的试图通过阻止任何写入命令(例如,以保护自身免受这样的情况下DEL),如果它是一个随机命令后执行(例如SCAN而且TIME,SRANDMEMBER和类似的)。我敢肯定有解决这个问题的方法,但是您想这样做吗?请记住,您将进入未定义系统行为的未知区域。
DEL
TIME
SRANDMEMBER
取而代之的是,接受一个事实,即您不应该将随机的读写混合使用,并尝试考虑一种解决问题的不同方法,即按照原子方式根据模式删除一堆密钥。
首先问问自己是否可以放宽任何要求。它必须是原子的吗?原子性意味着Redis在删除期间将被阻止(无论最终实现如何),并且操作的时间取决于作业的大小(即,删除的键数及其内容[删除大的键比删除短字符串更昂贵]])。
如果不需要原子性,则定期/懒惰SCAN并小批量删除。如果必须的话,请理解您基本上是在尝试模仿 邪恶的KEYS命令:)但是,如果您事先了解该模式,则可以做得更好。
KEYS
假设模式在您的应用程序运行时是已知的,那么您可以收集相关的密钥(例如,在Set中),然后使用该集合以原子安全且复制安全的方式实现删除,这比遍历整个密钥空间更有效。
但是,最“棘手”的问题是,您需要在确保原子性的同时运行临时模式匹配。如果是这样,则问题归结为立即获取键空间的按模式过滤的快照,然后立即进行一系列删除(重新强调:阻止数据库时)。在这种情况下,您可以很好地KEYS在Lua脚本中使用并希望获得最好的结果(但要知道,您可能SHUTDOWN NOSAVE很快就会诉诸:P)。
SHUTDOWN NOSAVE
最后的优化是索引键空间本身。两者SCAN和KEYS基本上都是全表扫描,那么如果我们要索引该表怎么办?想象一下,在交易期间可以查询的键名称上保留索引- 您可能可以使用排序集和字典范围(HT @TwBert )来消除大多数模式匹配需求。但是代价不菲……您不仅要进行双重记账(将每个键的名称成本存储在RAM和CPU中),而且还被迫增加应用程序的复杂性。为什么要增加复杂性?因为要实现这样的索引,您必须自己在应用程序层(以及可能的所有其他Lua脚本)中维护它,因此请在事务中仔细包装对Redis的每个写操作,该事务还会更新索引。
假设您做了所有这些工作(并考虑到了明显的陷阱,例如增加了复杂性的潜在错误,Redis,RAM和CPU的写入负载至少翻了一番,缩放限制等),您可以在肩并祝贺您使用非Redis设计的方式使用Redis。尽管即将发布的Redis版本可能(或可能没有)针对此挑战提供了更好的解决方案( @TwBert-想要进行RCP / contrib联合并再次破解Redis? ),但在尝试此操作之前,我真的敦促您重新考虑原始要求并确认您正确使用了Redis(即根据数据访问需求设计“模式”)。