我只是想了解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。
根据Redis的文档,您在原子性方面会没事的:
Redis使用相同的Lua解释器来运行所有命令。另外,Redis保证以原子方式执行脚本:执行脚本时不会执行其他脚本或Redis命令。这种语义类似于MULTI / EXEC中的一种。从所有其他客户端的角度来看,脚本的效果还是不可见或已经完成。
但是,如果脚本太慢,则会导致问题。因此,脚本是需要某些逻辑和原子性的轻型操作的最佳选择。
您可能会遇到的另一个漏洞是,如果脚本在中间某种程度上失败了,尽管脚本将返回错误,但您所做的那些调用将无法回滚。
例如:您有一个如下脚本:
redis.call('set', 'foo', 1) redis.call('rpush', 'foo', 2)
脚本执行将返回错误,但foo已在Redis中设置为1。
foo
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