对于事物在这就不多做概念性的介绍,自己可以去搜索。

对于Redis的事务简单说明一下

  • 事务作为一个整体被执行,执行期间不会被其它客户端请求打断
  • 事务中的多个命令打包执行,当做一个整体执行,不会被打断
  • 事务在执行的中途遇到错误,不会回滚,而是继续执行后续命令
  • 事务中可以穿插查询,根据结果执行不同操作

我们可以用lua脚本来实现事务处理

在go中使用lua脚本访问redis时以下几点要注意

  • 脚本返回nil时,Go中得到的是err = redis.Nil(与Get找不到值相同)
  • 脚本返回false时,Go中得到的是nil,脚本返回true时,Go中得到的是int64类型的1
  • 脚本返回number类型时,Go中得到的是int64类型
  • 传入脚本的KEYS/ARGV中的值一律为string类型,要转换为数字类型使用to_number
  • 脚本返回{"ok": ...}时,Go中得到的是redis的status类型(true/false)
  • 脚本返回{"err": ...}时,Go中得到的是err值,也可以通过return redis.error_reply("My Error")达成

测试代码

cache.go

package rds

import (
    "fmt"
    "time"

    rds "github.com/gomodule/redigo/redis"
)

type Cache struct {
    pool *rds.Pool
}

func InitCache(rdsAddress string, pwd string) *Cache {
    p := &rds.Pool{
        MaxIdle:     5,
        MaxActive:   30,
        IdleTimeout: 240 * time.Second,
        Dial: func() (rds.Conn, error) {
            return rds.Dial("tcp", rdsAddress, rds.DialPassword(pwd))
        },
    }

    return &Cache{
        pool: p,
    }
}

func (cache *Cache) GetCon() rds.Conn {
    return cache.pool.Get()
}

func (cache *Cache) Close() {
    cache.pool.Close()
}

func (cache *Cache) GetAndAdd(roomId string, usrId string) (usrAccPoint string, err error) {

    script := rds.NewScript(2, `
        local key1 = "usr:accpoint:" .. KEYS[2]
        local accPoint = redis.call("get", key1)

        key1 = "room:accpoints:" .. KEYS[1]
        redis.call("sadd", key1, accPoint)

        key1 = "room:accpoint:" .. KEYS[1]
        key1 = key1 .. ":" .. accPoint
        redis.call("sadd", key1, KEYS[2])

        return accPoint
    `)
    con := cache.GetCon()
    defer con.Close()

    usrAccPoint, err = rds.String(script.Do(con, roomId, usrId))
    if err != nil {
        fmt.Println(err.Error())
        return
    }

    return
}

func (cache *Cache) GetAndRem(roomId string, usrId string) (cnt int64, err error) {

    script := rds.NewScript(2, `
        local key1 = "usr:accpoint:" .. KEYS[2]
        local accPoint = redis.call("get", key1)

        key1 = "room:accpoint:" .. KEYS[1] .. ":" .. accPoint
        redis.call("srem", key1, KEYS[2])

        local cnt = redis.call("scard", key1)
        if (cnt == 0) then
            key1 = "room:accpoints:" .. KEYS[1]
            redis.call("srem", key1, accPoint)
        end

        return cnt
    `)
    con := cache.GetCon()
    defer con.Close()

    accPointCnt, err := rds.Int64(script.Do(con, roomId, usrId))
    if err != nil {
        fmt.Println(err.Error())
        return -1, err
    }

    fmt.Printf("Room:%s, remains %d accPoint\n", roomId, accPointCnt)

    return accPointCnt, nil
}

main.go

package main

import (
    "fmt"

    cache "jianghu.com/godemo/026redislua/rds"
)

const (
    REDIS_ADDR string = "127.0.0.1:6379"
)

func main() {
    c := cache.InitCache(REDIS_ADDR, "")
    defer c.Close()

    v, err := c.GetAndAdd("1", "100")
    if err != nil {
        fmt.Printf("Error:%s\n", err.Error())
    }
    fmt.Printf("Redis return v:%s\n", v)

    cnt, err := c.GetAndRem("1", "100")
    if err != nil {
        fmt.Printf("Error:%s\n", err.Error())
    }

    fmt.Printf("Cnt:%d\n", cnt)
}

有几个关键的方法说明一下

(1) 生成脚本

  • func rds.NewScript(keyCount int, src string) *rds.Script
    keyCount传入参数的个数,对应lua脚本中KEYS[1],KEYS[2]...KEYS[keyCount]

(2)执行脚本,如果脚本没有加载会自动调用加载处理

  • func (*rds.Script).Do(c rds.Conn, keysAndArgs ...interface{}) (interface{}, error)
    返回参数需要根据类型调用相应的转换处理

麦穗儿
127 声望15 粉丝

程序猿以技术为本