mediocregopher / radix.v2

Redis client for Go
http://godoc.org/github.com/mediocregopher/radix.v2
MIT License
433 stars 92 forks source link

how can i use this for transactions command 'watch,mutil,...,exec,unwatch' #44

Closed jiangyoucai closed 7 years ago

jiangyoucai commented 7 years ago

I am building a web store I use locks and transactions to ensure proper inventory But how do I use the transaction? I wrote the following code But I'm not sure this is right

    _ = cache.Conn.Cmd("WATCH", "5")
    d, err := cache.Conn.Cmd("GET", "5").Str()
    if err == nil && d == "5" {
        cache.Conn.PipeAppend("MULTI")
        cache.Conn.PipeAppend("SET", "key1", "1")
        cache.Conn.PipeAppend("SET", "key2", "2")
        cache.Conn.PipeAppend("SET", "key3", "3")
        cache.Conn.PipeAppend("SET", "key4", "4")
        cache.Conn.PipeAppend("SET", "key5", "5")
        cache.Conn.PipeAppend("EXEC")
        _ = cache.Conn.PipeResp()
    }
    cache.Conn.Cmd("UNWATCH")

Can you give me some suggestions ?

mediocregopher commented 7 years ago

So you're pretty close. I really recommend checking for errors at every step, even though it's a bit of a pain. Also you need to make sure you call PipeResp for every time that you've called PipeAppend, or use PipeClear if you know you don't care about the responses (but in this case you probably want to know if your transaction succeeded, so you do want the responses). So something like this:

    if err := cache.Conn.Cmd("WATCH", "5").Err; err != nil {
        // handle error
    }

    d, err := cache.Conn.Cmd("GET", "5").Str()
    if err != nil {
        // handle error
    }

    if d != "5" {
        if err := cache.Conn.Cmd("UNWATCH"); err != nil {
            // handle error
        }
        // handle d not being "5"
        return
    }

    cache.Conn.PipeAppend("MULTI")
    cache.Conn.PipeAppend("SET", "key1", "1")
    cache.Conn.PipeAppend("SET", "key2", "2")
    cache.Conn.PipeAppend("SET", "key3", "3")
    cache.Conn.PipeAppend("SET", "key4", "4")
    cache.Conn.PipeAppend("SET", "key5", "5")
    cache.Conn.PipeAppend("EXEC")

    for i := 6; i >= 0; i-- {
        r := cache.Conn.PipeResp()
        if r.Err != nil {
            // handle error
        } else if i == 0 {
            // This will be the actual response to EXEC. This is where you'll
            // find out if your transaction actually succeeded, if the reply is
            // nil it means it failed because "5" was changed
            if r.IsType(redis.Nil) {
                // transaction failed
            }
        }
    }

An alternative I'd really recommend is looking into the lua EVAL stuff. antirez has basically stated that lua is the way forward for people doing transactional stuff, the WATCH/MULTI/EXEC stuff is only left for backwards compatibility. It's still a bit of a pain because you have to learn lua, but you end up with much cleaner and simpler code. Lua eval is also faster in cases where you'd otherwise have to use WATCH, because you don't have to retry your transactions, they're always atomic. For your case you'd end up with something like this:

    script := `
        if redis.call("GET", "5") ~= "5" then
            return nil
        end

        for i = 1, #KEYS do
            redis.call("SET", KEYS[i], ARGV[i])
        end
        return "OK"
    `

    r := util.LuaEval(cache.Conn, script, 5,
        "key1", "key2", "key3", "key4", "key5",
        "1", "2", "3", "4", "5",
    )
    log.Print(r)

Note I hardcoded the check on "5", but you could make that part of the arguments to your script as well if that's something which changes. LuaEval is also nice because it'll work with a Pool or Cluster automatically.

Hope this helps, lemme know if you have any more questions :)

jiangyoucai commented 7 years ago

Usually, I use lua as a server deployment script and openresty widget I will try it with redis. thank you very much.

mediocregopher commented 7 years ago

No problem! If you're used to openresty then you should have no problem here, redis' lua support is way simpler. I'll go ahead and close this now, but feel free to open it back up if you've got any other questions.