Redis的Lua脚本实现CAS(检查和设置)?

7
我只是试图理解Redis/Lua脚本,我想知道是否有人在以下代码中看到了问题。
这是我尝试实现非常简单的"CAS"语义的代码: 只需使用一个键和两个参数调用它。它将检查服务器上与该键关联的值是否以第一个参数开头,如果是,则将键的新值设置为第二个参数并返回1,否则将返回0; 如果该键与除字符串以外的某种类型数据相关联,则Redis将像尝试在此类键/值组合上执行SET命令时一样返回错误。如果在调用之前该键不存在,则函数将返回0(失败)。
以下是脚本:
local x=string.len(ARGV[1]);
if redis.call('GETRANGE', KEYS[1], 0, x-1) == ARGV[1] then
    redis.call('SET', KEYS[1], ARGV[2]);
    return 1;
    end;
return 0

以下是关于如何在redis-cli中使用前缀值为“bar”调用键“foo”的脚本示例: redis-cli中的命令如下:
eval "local x=string.len(ARGV[1]); if redis.call('GETRANGE', KEYS[1], 0, x-1) == ARGV[1] then redis.call('SET', KEYS[1], ARGV[2]); return 1; end; return 0" 1 foo bar barbazzle

我认为这个用法可能是你想要存储"围栏标记(fencing token)"和带有键的值的情况...如果它们持有正确的围栏标记,允许并发客户端尝试更新该值。

如果没有WATCH / MULTI / EXEC语义,这是否似乎是一种安全的使用模式?(似乎可以获取当前值,在本地代码中分离出围栏标记,构建一个新值,然后随时尝试使用似乎比WATCH / MULTI / EXEC调用更不容易混淆的语义来更新密钥)。

(我知道我的脚本语义与memcached CAS命令略有不同;这是有意的。)

这在我的有限测试中确实通过了...所以我真正想问的是任何潜在的并发/原子性问题以及Lua中是否存在任何愚蠢的问题---因为我几乎从未接触过Lua。


非常好的问题,表述得无懈可击。只要键是字符串,就可以完全保证原子性/并发性/其他方面的正常运行。 - Itamar Haber
1
这里有一个有趣的讨论,与Salvatore Sanfilippo讨论了为什么Redis没有按设计实现CAS方法。 - bartolo-otrit
1个回答

4
根据Redis文档,在原子性方面你会很好。Redis使用相同的Lua解释器运行所有命令。此外,Redis保证脚本以原子方式执行:在执行脚本时不会执行其他脚本或Redis命令。这种语义类似于MULTI / EXEC。从所有其他客户端的角度来看,脚本的效果要么仍然不可见,要么已经完成。但是,如果脚本太慢,会导致问题。因此,脚本最适合轻量级操作,需要一些逻辑和原子性。你可能会掉入的另一个漏洞是,如果脚本在中间某个位置失败了,尽管脚本将返回错误,但你所做的那些调用可能无法回滚。例如:你有一个像这样的脚本:
redis.call('set', 'foo', 1)
redis.call('rpush', 'foo', 2)

脚本执行会返回错误,但是foo已经在redis中设置为1

我注意到您使用了一些与您的问题无关的内容:

eval "your_raw_code" key_count keys argv

实际上,当您在终端时,可以在eval中调用lua脚本文件:
> redis-cli eval "$(cat path/to/script/script_name.lua)" key_count keys argv

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接