我只是想了解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
这是一个在密钥上调用脚本的示例" foo"前缀值为" bar" (在 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
我认为这种情况的使用模式可能是您希望同时存储"屏蔽令牌"和一个带键的值...允许并发客户端在他们持有正确的防护令牌时尝试更新该值。
这似乎是一种安全的使用模式来代替WATCH / MULTI / EXEC语义吗? (看起来你可以获取当前值,在本地代码中拆分防护令牌,构建一个新值,然后尝试随时更新密钥,语义似乎比WATCH / MULTI / EXEC调用更混乱)。
(我知道我的脚本的语义与 memcached CAS命令略有不同;这是故意的。)
这确实通过了我的有限测试...所以我真的在询问任何潜在的并发/原子性问题以及Lua中是否有任何愚蠢的东西 - 因为我几乎没有过去曾感动过Lua。)
答案 0 :(得分:1)
Redis使用相同的Lua解释器来运行所有命令。此外,Redis保证脚本以原子方式执行:在执行脚本时不会执行其他脚本或Redis命令。这种语义类似于MULTI / EXEC。从所有其他客户端的角度来看,脚本的效果仍然不可见或已经完成。
但是,如果脚本太慢,则会导致问题。所以脚本最适合需要逻辑和原子性的轻型操作。
你可能陷入的另一个漏洞是,如果脚本在某种程度上失败了,那么你完成的那些调用无法回滚,尽管脚本会返回错误。
E.g: 你有一个这样的脚本:
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