How can I read from Redis inside a MULTI block in Ruby?

前端 未结 2 675
终归单人心
终归单人心 2021-02-08 10:07

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 transa

相关标签:
2条回答
  • 2021-02-08 10:20

    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.

    There are two ways to implement such transaction.

    Using a WATCH clause

    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.

    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

    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:

    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.

    0 讨论(0)
  • 2021-02-08 10:27

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

    Either all of the commands or none are processed.

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

    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.

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

    0 讨论(0)
提交回复
热议问题