对于事物在这就不多做概念性的介绍,自己可以去搜索。
对于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)
返回参数需要根据类型调用相应的转换处理
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。