一尘不染

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

redis

我只是想了解Redis / Lua脚本,我想知道是否有人看到以下代码有问题。

我尝试实现非常简单的“ CAS”语义:使用单个键和两个参数来调用它。它将检查服务器上与该键关联的值是否 第一个参数 开头
,如果是,则将设置键的新值设置为第二个参数并返回1,否则返回0;否则返回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

这是在前缀值为“ bar”(在 redis-cli中 )的键“ foo”上调用脚本的示例:

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

我认为这种用法模式可能是您想同时存储“围栏令牌”和带有键的值的情况……允许并发客户端在持有正确的围栏令牌的情况下尝试更新该值。

看起来这将是代替WATCH / MULTI /
EXEC语义的安全使用模式吗?(似乎您可以获取当前值,在本地代码中拆分防护令牌,构建新值,然后尝试在任何时候以比WATCH / MULTI /
EXEC调用少的语义来尝试更新密钥)。

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

这确实通过了我的有限测试……所以我真的是在问任何潜在的并发/原子性问题,以及Lua中是否有任何愚蠢的东西,因为我过去几乎没有碰过Lua。


阅读 787

收藏
2020-06-20

共1个答案

一尘不染

根据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
2020-06-20