我在MULTI事务中封装了一组复杂的Redis命令,但事务中的逻辑依赖于Redis中已有的值。但是事务中的所有读取似乎都返回nil
以下是演示此问题的示例:
[Dev]> $redis.set("foo", "bar")
=> "OK"
[Dev]> $redis.multi{ $redis.set("foo", "baz") if $redis.get("foo") == "bar" }
=> ["bar"]
[Dev]> $redis.get("foo")
=> "bar"
显然我希望最后一个返回值为'baz'
- 我该如何实现?
答案 0 :(得分:19)
你不能,因为所有命令(包括get)实际上都是在exec时执行的。在这种情况下,get命令只返回future对象,而不是实际值。
有两种方法可以实现此类交易。
使用WATCH条款
watch子句用于防止并发更新。如果在watch和multi子句之间更新变量的值,则不应用多块中的命令。由客户端再次尝试交易。
loop do
$redis.watch "foo"
val = $redis.get("foo")
if val == "bar" then
res = $redis.multi do |r|
r.set("foo", "baz")
end
break if res
else
$redis.unwatch "foo"
break
end
end
这里的脚本有点复杂,因为块的内容可能是空的,因此没有简单的方法可以知道事务是否已被取消,或者它是否根本没有发生。除非交易被取消,否则多块在所有情况下都会返回结果通常会更容易。
使用Lua服务器端脚本
使用Redis 2.6或更高版本,Lua scripts can be executed on the server。整个脚本的执行是原子的。它可以很容易地在Ruby中实现:
cmd = <<EOF
if redis.call('get',KEYS[1]) == ARGV[1] then
redis.call('set',KEYS[1],ARGV[2] )
end
EOF
$redis.eval cmd, 1, "foo", "bar", "baz"
这通常比使用WATCH子句简单得多。
答案 1 :(得分:1)
正如Sergio在评论中指出的那样,你不能选择像Redis那样执行MULTI
块。请参阅documentation on transactions:
处理所有命令或不处理任何命令。
但是,您可以使用WATCH
使用check-and-set(伪代码)实现乐观锁定:
SET foo bar
WATCH foo
$foo = GET foo
MULTI
if $foo == 'bar'
SET foo baz
EXEC
GET foo
使用WATCH
,只有在未更改被监视的密钥时才会执行事务。如果更改了监视密钥,EXEC
将失败,您可以重试。
另一种可能性是使用脚本功能,但这仅在2.6版本候选版本中可用。