13

我在 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'——我怎样才能做到这一点?

4

2 回答 2

21

您不能,因为所有命令(包括 get)实际上都是在 exec 时执行的。在这种情况下,get 命令只返回一个未来对象,而不是实际值。

有两种方法可以实现这种交易。

使用 WATCH 子句

watch 子句用于防止并发更新。如果变量的值在 watch 和 multi 子句之间更新,则不会应用 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 脚本。整个脚本的执行是原子的。它可以在 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 子句简单得多。

于 2012-07-03T12:40:58.443 回答
1

正如 Sergio 在他的评论中所说,您不能选择MULTI在 Redis 中执行这样的块。请参阅有关交易的文档

要么处理所有命令,要么不处理任何命令。

但是,您可以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 候选版本中可用。

于 2012-07-03T12:05:37.657 回答