redis-----07-----redigo基本命令操作(主要讲如何让go的struct、map展开成redis的参数,以及使用struct获取redis返回的key-value批量数组)
1 请求回应模式redis 与 client 之间采用请求回应模式,一个请求包对应一个回应包。但是也有例外,pub/sub 模式下,client 发送 subscribe 命令并收到回应包后,之后被动接收 redis 的发布包。所以若需要使用 pub/sub 模式,那么需要在 client 下创建额外一条连接与 redis 交互。2 Redis 协议图redis 协议采用特殊字符( \r\n )来
1 请求回应模式
redis 与 client 之间采用请求回应模式,一个请求包对应一个回应包。但是也有例外,pub/sub 模式下,client 发送 subscribe 命令并收到回应包后,之后被动接收 redis 的发布包。所以若需要使用 pub/sub 模式,那么需要在 client 下创建额外一条连接与 redis 交互。
2 Redis 协议图
redis 协议采用特殊字符( \r\n )来分割有意义的数据,redis 使用的是二进制安全字符串(用长度来标识字符串的长度),所以 redis 可以用来存储二进制数据(比如经过压缩算法加密的数据)。
- 1)例如上图客户端。星号*代表后面紧接着的字段的参数数量,包括关键字本身,\r\n代表该字段结束。$符号代表下一个字段参数的字节数,\r\n代表该字段结束。读到和参数数量的字段后,剩余的就是数据了。
set key val
# 上面的命令用上图的redis协议可以这样表示:
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$3\r\nval\r\n"
- 2)服务端的同理,+号代表简单字符串的回复。-号代表错误的回复,:号代表整数回复,$号代表有数据回复,和上面## 标题讲的一样。*号同理,看上面即可。
例如简单字符串回复、错误回复、整数回复的redis例子。
# 整数回复
192.168.1.9:6379> del tyy
(integer) 1
# 简单字符串回复
192.168.1.9:6379> set tyy hi
OK
# 错误回复
192.168.1.9:6379> ZADD tyy 1 one
(error) WRONGTYPE Operation against a key holding the wrong kind of value
192.168.1.9:6379>
# 二进制字符串的返回可以通过get等命令进行测试。$-1代表返回的是nil值。
# 批量回复可以使用zrange key 0 -1 withscores这个命令进行测试。*0代表返回空数组。*-1代表超时(brpop可以测这个超时)。其它就是正常返回数据。
3 Redigo
golang 的 redis 驱动实现了 go 应用程序与 redis 服务器通信的协议实现,同时实现了与 redis
交互时的数据转换。所以我们可以使用Redigo这个库来进行与redis交互。
1)git 地址:https://github.com/gomodule/redigo.git
2)文档地址:https://pkg.go.dev/github.com/garyburd/redigo@v1.6.2/redis
3.1 命令执行
想要执行redis中的命令,我们可以统一使用Redigo里面的Do函数。
Do(commandName string, args ...interface{}) (reply interface{}, err error)
3.2 参数类型转换
- 1)go的字节切片[]byte:因为go的字节切片是使用字节流的方式传输,所以在与redis进行通信时,无须转换。
- 2)go的string字符串:go的字符串和redis无须转换。
- 3)go的int、int64:如果go的类型是int、int64时,需要使用redigo的strconv.FormatInt函数进行转换。
- 4)go的float64浮点数:如果go的类型是float64时,需要使用redigo的strconv.FormatFloat64函数进行转换。
- 5)go的布尔类型:如果go的类型是bool时,需要转成对应的1和0。
- 6)go的其它类型:看情况进行转换。具体可以看上面Redigo的文档。
3.3 返回值类型转换
返回值一般是针对于redis返回给go,所以我们主要分析redis返回的错误如何用go的类型结束即可。
- 1)redis返回错误类型:redis的错误类型我们可以使用Redigo封装好的redis.Error这个东西获取即可。
- 2)redis返回integer:go中使用int64接收。
- 3)redis返回simple string简单字符串:go中使用string接收。
- 4)redis返回bulk string批量字符串:go中使用字节切片接收。
- 5)redis返回array数组:go中使用[]interface{}空切片接口这个万能类型去接收。
具体redis的返回的返回值,go该使用什么类型接收同样可以参考上面Redigo的文档。
4 重点
- 1)redis是一种请求回应模式。
- 2)redis的string是二进制安全字符串。
- 3)协议传输过程中,是字节流,对应 go 类型为 []byte 。redis 协议需要将整数、浮点数都会转换为字符串。
- 4)redis.Args 方便我们组织复杂的参数类型,能将 struct 和 map 通过redis.Args{}.AddFlat 进行展开。
- 5)如果接收到 redis-server 批量回复(array):
- 类型相同的可以使用 redis.Ints redis.Float64s 等类型名后加上 s 来接收返回值。
- 类型固定且有关联性,比如接收 hash 结构的返回值,则可以使用 redis.StringMap 、 redis.IntMap 等。
- 类型差异大但是有规律,可以使用 redis.Values + redis.ScanStruct 来接收。
- 简单批量回复且类型不同,可以使用 redis.Valus + redis.Scan 来接收。
- 接收多个结构体,可以使用 redis.Values + redis.ScanSlice 来接收。
但是这些转换的内容无须死记,用到对应的转换时,具体看回下面的操作代码即可。
5 redigo基本命令操作
package main
import (
"fmt"
"reflect"
"github.com/garyburd/redigo/redis"
)
func main() {
// 1. 连接redis
//conn, err := redis.Dial("tcp", fmt.Sprintf("%s:%d", "172.20.10.9", 6379))
conn, err := redis.Dial("tcp", "192.168.1.9:6379", redis.DialPassword("123456"))
if err != nil {
panic(err)
}
defer (func() {
fmt.Println("connection close")
conn.Close()
})()
// 2. 执行命令并获取该key的值。
if false {
//conn.Do("set", "hello", 1)
// 如果不加redis.Int,那么conn.DO返回的是切片类型[]type,因为redis和go使用tcp字节流传输
//rpy, err := conn.Do("get", "hello")
//rpy, err := redis.Int(conn.Do("get", "hello"))
conn.Do("set", "hello", "world")
// 如果不加redis.String,那么conn.DO返回的是字节切片类型[]type(整数和单个字符串一般是[]uint8),因为redis和go使用tcp字节流传输
//rpy, err := conn.Do("get", "hello")
rpy, err := redis.String(conn.Do("get", "hello"))
if err != nil {
panic(err)
}
fmt.Println(rpy, reflect.TypeOf(rpy))
}
// 3. 使用redis.Args{}添加多个参数。
if false {
conn.Do("del", "list")
args := redis.Args{}.Add("list").Add("tyy").Add("lqq").Add("hc")
conn.Do("lpush", args...)
// 因为Do返回的是批量字符串,所以我们可以使用Strings来转成[]string类型的切片。切片可以保存二进制数据。
rpy, _ := redis.Strings(conn.Do("lrange", "list", 0, -1))
fmt.Println(rpy, reflect.TypeOf(rpy))
}
// 4. 测试brpop,并且使用[]string字符串切片进行接收。
// 测试方法:if条件改成true,运行程序,然后xshell开启redis-cli往这个list输入个数据即可。
// 例如命令为:lpush list lqq tyy hc。注意如果一次lpush多个,下面的brpop只会拿走一个就返回,
// brpop和rpop的区别就在于为空时是否阻塞。
// 例如结果:[list lqq]
// <nil>
// connection close
// list代表key,lqq代表获取到的名字,<nil>和connection close只是err的值,返回nil代表没错误,然后连接关闭。
if false {
conn.Do("del", "list")
vals, err := redis.Strings(conn.Do("brpop", redis.Args{}.Add("list").Add(20)...))
if err != redis.ErrNil {
fmt.Println(vals)
fmt.Println(err)
}
}
// 5. 同样测试brpop,但使用map[string]string进行接收。大家可以按照需求来使用不同方式接收。
// 测试方法:同第4点。
if false {
conn.Do("del", "list")
vals, err := redis.StringMap(conn.Do("brpop", redis.Args{}.Add("list").Add(20)...))
if err != redis.ErrNil {
fmt.Println(vals)
fmt.Println(err)
}
}
// 6. 如果redis返回的是 批量数组 且 类型不同的内容,且 我们知道它具体的返回类型 时,满足这3个条件,
// 那么可以使用redis.Values + redis.Scan组合。
if false {
conn.Do("del", "list")
conn.Do("lpush", "list", "tyy", 100) // 往list中push字符串和整型。
vals, _ := redis.Values(conn.Do("lrange", "list", 0, -1))
var name string
var score int
redis.Scan(vals, &score, &name) // 注意顺序,因为先返回100,再返回tyy,使用参数2位score,参数3为name
fmt.Println(name, score)
}
// 7. 结构体、map作为redis的命令数据传入,并且利用结构体重新从redis中读取返回的key-value数据。
if true {
// 7.1 将结构体展开并当做redis的参数,传输到redis中执行。
var p1, p2 struct {
// 使用strurt_tag,类似json(`json:"company"`),那么字段名Name会变成name。
Name string `redis:"name"`
Age string `redis:"age"`
Sex string `redis:"sex"`
}
p1.Age = "18"
p1.Name = "tyy"
p1.Sex = "male"
// AddFlat的作用是将结构体的字段名和字段值进行展开,例如展开p1之后:age 18 name tyy sex male
args1 := redis.Args{}.Add("role:10001").AddFlat(&p1)
if _, err := conn.Do("hmset", args1...); err != nil {
fmt.Println(err)
return
}
// 7.2 将map展开并当做redis的参数,传输到redis中执行。
m := map[string]string{
"name": "lqq",
"age": "20",
"sex": "female",
}
args2 := redis.Args{}.Add("role:10002").AddFlat(m)
if _, err := conn.Do("hmset", args2...); err != nil {
fmt.Println(err)
return
}
// 7.3 使用结构体从redis中获取key-value的内容。
// 由于redis返回的是key-value形式的批量数组,那么我们可以使用 redis.Values + redis.ScanStruct方式获取内容。
// 参考redis命令行输入会返回以下内容:
/*
192.168.1.9:6379> HGETALL role:10001
1) "name"
2) "lqq"
3) "age"
4) "20"
5) "sex"
6) "female"
*/
for _, id := range []string{"role:10001", "role:10002"} { // id是"role:10001", "role:10002"
fmt.Println(id)
v, err := redis.Values(conn.Do("HGETALL", id))
if err != nil {
fmt.Println(err)
return
}
if err := redis.ScanStruct(v, &p2); err != nil {
fmt.Println(err)
return
}
fmt.Printf("%+v\n", p2)
}
}
}
上面代码步骤依次对应的结果截图(4、5的brpop需要在shell的命令行往list中lpush后,程序才能不超时返回):
-
2)
-
3)
-
4)
-
5)
-
6)
-
7)
更多推荐
所有评论(0)