Redis Lua 脚本实现 CAS(检查并设置)?
Redis Lua script implementing CAS (check-and-set)?
我只是想了解 Redis/Lua 脚本,我想知道是否有人发现以下代码有问题。
这是我尝试实现非常简单的 "CAS" 语义:用一个键和两个参数调用它。它将检查服务器上与该键关联的值是否 以第一个参数 开头,如果是,则将键的新值设置为第二个参数和 return 1 否则会 return 0;如果键与字符串以外的某种类型的数据相关联,则 Redis 将 return 并出错,就像您尝试对此类 key/value 组合执行 SET 命令一样。如果密钥在调用之前不存在,则该函数将 return 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
我认为这种使用模式可能是您希望同时存储 "fencing token" 和一个带有键的值的情况...允许并发客户端尝试更新该值(如果它们持有)正确的击剑标记。
这看起来像是一种安全的使用模式来代替 WATCH/MULTI/EXEC 语义吗? (看起来你可以获取当前值,在你的本地代码中分离出 fencing 标记,构建一个新值,然后在你喜欢的任何时候尝试更新密钥,语义似乎比 WATCH/MULTI/EXEC 调用更容易混淆)。
(我知道我的脚本的语义与 memcached CAS 命令略有不同;这是故意的)。
这确实通过了我的有限测试...所以我真的想问任何潜在的 concurrency/atomicity 问题以及 Lua 中是否有任何愚蠢的东西 --- 因为我几乎从来没有过去接触过Lua。
,你在原子性方面会很好
Redis uses the same Lua interpreter to run all the commands. Also Redis guarantees that a script is executed in an atomic way: no other script or Redis command will be executed while a script is being executed. This semantic is similar to the one of MULTI / EXEC. From the point of view of all the other clients the effects of a script are either still not visible or already completed.
但是,如果脚本太慢,就会出现问题。所以脚本最适合需要一些逻辑和原子性的轻型操作。
您可能会陷入的另一个漏洞是,如果脚本在中间不知何故失败,您所做的那些调用将无法回滚,尽管脚本会 return 错误。
例如:
您有这样的脚本:
redis.call('set', 'foo', 1)
redis.call('rpush', 'foo', 2)
脚本执行会return错误,但是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
我只是想了解 Redis/Lua 脚本,我想知道是否有人发现以下代码有问题。
这是我尝试实现非常简单的 "CAS" 语义:用一个键和两个参数调用它。它将检查服务器上与该键关联的值是否 以第一个参数 开头,如果是,则将键的新值设置为第二个参数和 return 1 否则会 return 0;如果键与字符串以外的某种类型的数据相关联,则 Redis 将 return 并出错,就像您尝试对此类 key/value 组合执行 SET 命令一样。如果密钥在调用之前不存在,则该函数将 return 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
我认为这种使用模式可能是您希望同时存储 "fencing token" 和一个带有键的值的情况...允许并发客户端尝试更新该值(如果它们持有)正确的击剑标记。
这看起来像是一种安全的使用模式来代替 WATCH/MULTI/EXEC 语义吗? (看起来你可以获取当前值,在你的本地代码中分离出 fencing 标记,构建一个新值,然后在你喜欢的任何时候尝试更新密钥,语义似乎比 WATCH/MULTI/EXEC 调用更容易混淆)。
(我知道我的脚本的语义与 memcached CAS 命令略有不同;这是故意的)。
这确实通过了我的有限测试...所以我真的想问任何潜在的 concurrency/atomicity 问题以及 Lua 中是否有任何愚蠢的东西 --- 因为我几乎从来没有过去接触过Lua。
Redis uses the same Lua interpreter to run all the commands. Also Redis guarantees that a script is executed in an atomic way: no other script or Redis command will be executed while a script is being executed. This semantic is similar to the one of MULTI / EXEC. From the point of view of all the other clients the effects of a script are either still not visible or already completed.
但是,如果脚本太慢,就会出现问题。所以脚本最适合需要一些逻辑和原子性的轻型操作。
您可能会陷入的另一个漏洞是,如果脚本在中间不知何故失败,您所做的那些调用将无法回滚,尽管脚本会 return 错误。
例如: 您有这样的脚本:
redis.call('set', 'foo', 1)
redis.call('rpush', 'foo', 2)
脚本执行会return错误,但是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