如何从Ruby中的MULTI块中读取Redis?

tws*_*tws 10 ruby transactions redis

我在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"
Run Code Online (Sandbox Code Playgroud)

显然我想要最后一个返回值'baz'- 我怎样才能实现这个目标?

Did*_*zia 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
Run Code Online (Sandbox Code Playgroud)

这里的脚本有点复杂,因为块的内容可能是空的,因此没有简单的方法可以知道事务是否已被取消,或者它是否根本没有发生.除非事务被取消,否则多块在所有情况下都会返回结果通常会更容易.

使用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"
Run Code Online (Sandbox Code Playgroud)

这通常比使用WATCH子句简单得多.


Lin*_*iel 1

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

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

但是,您可以WATCH使用检查和设置(伪代码)来实现乐观锁定:

SET foo bar
WATCH foo
$foo = GET foo
MULTI
if $foo == 'bar'
  SET foo baz
EXEC
GET foo
Run Code Online (Sandbox Code Playgroud)

使用WATCH,仅当监视的密钥未更改时才会执行事务。如果更改手表密钥,则会EXEC失败,您可以重试。

另一种可能性是使用脚本功能,但这仅在 2.6 候选版本中可用。