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

时间:2021-09-15 05:24:16

I'm encapsulating a complicated set of Redis commands in a MULTI transaction, but the logic in the transaction depends on values already in Redis. But all reads within a transaction seem to return nil

我在MULTI事务中封装了一组复杂的Redis命令,但事务中的逻辑依赖于Redis中已有的值。但是交易中的所有读取似乎都返回零

Here's an example that demonstrates the problem:

这是一个演示问题的示例:

[Dev]> $redis.set("foo", "bar")
=> "OK"
[Dev]> $redis.multi{ $redis.set("foo", "baz") if $redis.get("foo") == "bar" }
=> ["bar"]
[Dev]> $redis.get("foo")
=> "bar"

Obviously I want the last return value to be 'baz' – how can I achieve this?

显然我希望最后一个返回值是'baz' - 我怎么能实现这个目标?

2 个解决方案

#1


17  

You cannot, since all commands (including get) are actually executed at exec time. In this situation, the get command only returns a future object, not the actual value.

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

There are two ways to implement such transaction.

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

Using a WATCH clause

使用WATCH子句

The watch clause is used to protect against concurrent updates. If the value of the variable is updated between the watch and multi clause, then the commands in the multi block are not applied. It is up to the client to attempt the transaction another time.

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

Here the script is a bit complex because the content of the block can be empty, so there is no easy way to know whether the transaction has been cancelled, or whether it did not take place at all. It is generally easier when the multi block returns results in all cases except if the transaction is cancelled.

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

Using Lua server-side scripting

使用Lua服务器端脚本

With Redis 2.6 or better, Lua scripts can be executed on the server. The execution of the whole script is atomic. It can be easily implemented in Ruby:

使用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"

This is typically much simpler than using WATCH clauses.

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

#2


1  

As Sergio states in his comment, you can't optionally execute a MULTI block like that in Redis. See the documentation on transactions:

正如塞尔吉奥在评论中指出的那样,你不能选择像Redis那样执行MULTI块。请参阅有关交易的文档:

Either all of the commands or none are processed.

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

You can, however, use WATCH to implement optimistic locking using check-and-set (pseudo code):

但是,您可以使用WATCH来使用check-and-set(伪代码)实现乐观锁定:

SET foo bar
WATCH foo
$foo = GET foo
MULTI
if $foo == 'bar'
  SET foo baz
EXEC
GET foo

Using WATCH, the transaction will only be executed if the watched key(s) has not been changed. If the watch key is changed, the EXEC will fail, and you can try again.

使用WATCH,仅当观察的密钥未被更改时才会执行事务。如果更改了监视键,EXEC将失败,您可以再试一次。

Another possibility is using the scripting functionality, but that's only available in the 2.6 release candidate.

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

#1


17  

You cannot, since all commands (including get) are actually executed at exec time. In this situation, the get command only returns a future object, not the actual value.

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

There are two ways to implement such transaction.

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

Using a WATCH clause

使用WATCH子句

The watch clause is used to protect against concurrent updates. If the value of the variable is updated between the watch and multi clause, then the commands in the multi block are not applied. It is up to the client to attempt the transaction another time.

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

Here the script is a bit complex because the content of the block can be empty, so there is no easy way to know whether the transaction has been cancelled, or whether it did not take place at all. It is generally easier when the multi block returns results in all cases except if the transaction is cancelled.

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

Using Lua server-side scripting

使用Lua服务器端脚本

With Redis 2.6 or better, Lua scripts can be executed on the server. The execution of the whole script is atomic. It can be easily implemented in Ruby:

使用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"

This is typically much simpler than using WATCH clauses.

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

#2


1  

As Sergio states in his comment, you can't optionally execute a MULTI block like that in Redis. See the documentation on transactions:

正如塞尔吉奥在评论中指出的那样,你不能选择像Redis那样执行MULTI块。请参阅有关交易的文档:

Either all of the commands or none are processed.

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

You can, however, use WATCH to implement optimistic locking using check-and-set (pseudo code):

但是,您可以使用WATCH来使用check-and-set(伪代码)实现乐观锁定:

SET foo bar
WATCH foo
$foo = GET foo
MULTI
if $foo == 'bar'
  SET foo baz
EXEC
GET foo

Using WATCH, the transaction will only be executed if the watched key(s) has not been changed. If the watch key is changed, the EXEC will fail, and you can try again.

使用WATCH,仅当观察的密钥未被更改时才会执行事务。如果更改了监视键,EXEC将失败,您可以再试一次。

Another possibility is using the scripting functionality, but that's only available in the 2.6 release candidate.

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