Redis - go
请求回应模式
redis 与 client 之间采用请求回应模式,一个请求包对应一个回应包;但是也有例外,pub/sub 模式下,client 发送 subscribe 命令并收到回应包后,之后被动接收 redis 的发布包;所以若需要使用 pub/sub 模式,那么需要在 client 下创建额外一条连接与 redis 交互;
在Redis中,客户端发送请求给服务器,然后服务器回应请求。在Go中,我们使用 Do 方法发送请求并接收响应。如果只有一个返回值,可以直接处理;如果有多个返回值,可以使用 redis.Values 来接收并处理。
Redis 协议图
redis 协议采用特殊字符(\r\n )来分割有意义的数据,redis 使用的是二进制安全字符串(用长度来标识字符串的长度),所以 redis 可以用来存储二进制数据(比如经过压缩算法加密的数据);
由于 Redis 的字符串结构是二进制安全的,这意味着它们可以存储任意的二进制数据,包括经过压缩或加密的数据。因此,Redis 可以用来存储和传输各种类型的二进制数据,如图像、视频、音频、压缩文件等。
# 存储压缩后的数据
SET compressed_data "压缩后的数据"
# 检索并解压数据
GET compressed_data
set key val
# "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$3\r\nval\r\n"
Redis 协议的序列化表示形式,它表示一个 SET 命令和对应的参数。
让我们解析一下这个字符串:
*3\r\n:表示后续有 3 个元素(即命令和参数的数量)。
$3\r\nSET\r\n:表示第一个参数是一个长度为 3 的字符串,其值为 “SET”。
$3\r\nkey\r\n:表示第二个参数是一个长度为 3 的字符串,其值为 “key”。
$3\r\nval\r\n:表示第三个参数是一个长度为 3 的字符串,其值为 “val”。
Redigo
git 地址:
https://github.com/gomodule/redigo.git
文档地址:https://pkg.go.dev/github.com/garyburd/redigo@v1.6.2/redis
golang 的 redis 驱动;实现了 go 应用程序与 redis 服务器通信的协议实现,同时实现了与 redis
交互时的数据转换
命令执行
要在 Go 中执行 Redis 命令,您可以使用 github.com/gomodule/redigo/redis 包,这是一个流行的 Redis 客户端库。
func (c Conn) Do(commandName string, args ...interface{}) (reply interface{}, err error)
c:Conn 类型的接收器,表示与 Redis 服务器的连接。
commandName:要执行的 Redis 命令的名称,例如 “SET”、“GET”、“LPUSH” 等。
args …interface{}:可变参数列表,用于传递 Redis 命令的参数。参数类型为 interface{},因此您可以传递任何类型的参数,例如字符串、整数、数组等。
reply interface{}:命令执行后的返回值,其类型为 interface{},因此可以根据命令的不同进行类型断言来获取具体的返回值类型。
err error:执行命令过程中可能发生的错误。如果命令执行成功,则 err 为 nil;否则,它将包含一个描述错误原因的非空错误值。
使用 Do 方法时,您需要传递命令的名称以及对应的参数。根据命令的不同,参数的数量和类型也会有所变化。例如,执行 SET 命令时,需要传递键名和键值;执行 LPUSH 命令时,需要传递列表名和要推入列表的值等。
// 设置键值对
reply, err := conn.Do("SET", "foo", "bar")
// 获取键值对的值
value, err := redis.String(conn.Do("GET", "foo"))
// 将元素推入列表
reply, err := conn.Do("LPUSH", "mylist", "value1", "value2", "value3")
使用 github.com/gomodule/redigo/redis 包。下面是一个简单的示例,展示了如何连接到 Redis 服务器并执行一些基本的操作:
package main
import (
"fmt"
"github.com/gomodule/redigo/redis"
)
func main() {
// 连接到 Redis 服务器
conn, err := redis.Dial("tcp", "localhost:6379")
if err != nil {
fmt.Println("Failed to connect to Redis:", err)
return
}
defer conn.Close()
// 设置键值对
_, err = conn.Do("SET", "foo", "bar")
if err != nil {
fmt.Println("Failed to set key:", err)
return
}
// 获取键值对的值
value, err := redis.String(conn.Do("GET", "foo"))
if err != nil {
fmt.Println("Failed to get key:", err)
return
}
fmt.Println("Value of 'foo':", value)
}
上述代码首先连接到本地运行的 Redis 服务器,然后设置了一个键值对 “foo”-“bar”,最后获取了键 “foo” 的值并将其打印输出。
可以根据需要执行其他 Redis 命令,只需使用 conn.Do 方法并传递命令名称及其参数即可。例如,要执行 LPUSH 命令将元素推入列表,可以这样做:
_, err = conn.Do("LPUSH", "mylist", "value1", "value2", "value3")
if err != nil {
fmt.Println("Failed to push elements to list:", err)
return
}
在Go语言中,[]byte 表示字节切片,用于存储二进制数据。而 tcp 则是传输控制协议,是一种面向连接的、可靠的、基于字节流的传输层通信协议。
当通过 TCP 连接发送数据时,可以使用 []byte 来表示数据,并通过 TCP 传输字节流。下面是一个简单的示例,展示了如何在 Go 中使用 net 包与 TCP 连接交互:
package main
import (
"fmt"
"net"
)
func main() {
// 连接到远程 TCP 服务器
conn, err := net.Dial("tcp", "example.com:8080")
if err != nil {
fmt.Println("Failed to connect to server:", err)
return
}
defer conn.Close()
// 准备要发送的数据,使用 []byte 表示字节流
data := []byte("Hello, server!")
// 将数据通过 TCP 连接发送给服务器
_, err = conn.Write(data)
if err != nil {
fmt.Println("Failed to send data:", err)
return
}
fmt.Println("Data sent successfully!")
}
在上面的示例中,我们使用 net.Dial 方法连接到一个名为 “example.com” 的服务器的 8080 端口。然后,我们准备了一个字符串数据 “Hello, server!”,并将其转换为 []byte 类型的字节流。最后,我们使用连接的 Write 方法将数据发送给服务器。
参数类型转换
在Go语言中,与Redis交互时,不同的Go类型需要以特定的方式转换为Redis参数类型。以下是常见类型转换的示例:
- 将字符串直接发送给Redis:
str := "hello"
// 直接将字符串转换为字节切片([]byte)发送
conn.Do("SET", "key", []byte(str))
// 或者直接将字符串发送
conn.Do("SET", "key", str)
- 将整数类型转换为字符串后发送给Redis:
num := 42
// 使用strconv.FormatInt将整数转换为字符串后发送
conn.Do("SET", "key", strconv.FormatInt(int64(num), 10))
- 将浮点数类型转换为字符串后发送给Redis:
f := 3.14
// 使用strconv.FormatFloat将浮点数转换为字符串后发送
conn.Do("SET", "key", strconv.FormatFloat(f, 'g', -1, 64))
- 将布尔类型转换为字符串后发送给Redis:
b := true
// 使用条件语句将布尔值转换为字符串后发送
if b {
conn.Do("SET", "key", "1")
} else {
conn.Do("SET", "key", "0")
}
- 发送空值或nil给Redis:
// 发送空字符串给Redis
conn.Do("SET", "key", "")
// 或者发送nil给Redis
conn.Do("SET", "key", nil)
- 对于其他类型,可以使用fmt包的Fprint函数将其转换为字符串后发送给Redis:
// 使用fmt.Fprint将其他类型转换为字符串后发送
someValue := SomeType{}
conn.Do("SET", "key", fmt.Sprint(someValue))
返回值转换
在Go语言中,从Redis获取的不同类型的返回值需要以特定的方式转换为Go类型。以下是常见类型转换的示例:
- 将错误转换为redis.Error类型:
// 从Redis获取返回值
reply, err := conn.Do("GET", "key")
if err != nil {
// 错误处理
if redisErr, ok := err.(redis.Error); ok {
// 如果是redis.Error类型的错误,则进行处理
fmt.Println("Redis error:", redisErr.Error())
} else {
// 否则打印普通错误信息
fmt.Println("General error:", err.Error())
}
}
- 将整数值转换为int64类型:
// 从Redis获取返回值
reply, err := conn.Do("GET", "integerKey")
if err != nil {
// 错误处理
}
// 将返回值转换为int64类型
intValue, _ := redis.Int64(reply, err)
- 将简单字符串转换为字符串类型:
// 从Redis获取返回值
reply, err := conn.Do("GET", "stringKey")
if err != nil {
// 错误处理
}
// 将返回值转换为字符串类型
stringValue, _ := redis.String(reply, err)
- 将批量字符串转换为字节切片([]byte)类型:
// 从Redis获取返回值
reply, err := conn.Do("GET", "bulkStringKey")
if err != nil {
// 错误处理
}
// 将返回值转换为字节切片([]byte)类型
byteValue, _ := redis.Bytes(reply, err)
- 将数组转换为[]interface{}类型:
// 从Redis获取返回值
reply, err := conn.Do("LRANGE", "listKey", 0, -1)
if err != nil {
// 错误处理
}
// 将返回值转换为[]interface{}类型
arrayValue, _ := redis.Values(reply, err)
参数处理
- 对于单个字符串和字节切片([]byte)类型的参数,可以直接传递,不需要进行额外的处理。
例如:
conn.Do("SET", "key", "value")
conn.Do("SET", "key", []byte("value"))
- 对于整数和浮点数类型的参数,需要先将其转换为字符串类型,然后再传递给 Redis。
例如:
num := 42
conn.Do("SET", "key", strconv.Itoa(num))
f := 3.14
conn.Do("SET", "key", strconv.FormatFloat(f, 'g', -1, 64))
- 使用 redis.Args 类型可以更方便地处理多个参数。它提供了 Add 方法来添加单个元素,并提供了 AddFlat
方法来处理结构体和 map 类型的参数。
例如:
args := redis.Args{}.Add("SET").Add("key").Add("value")
conn.Do(args...)
// 使用 AddFlat 添加结构体或 map 参数
type Person struct {
Name string
Age int
}
p := Person{Name: "Alice", Age: 30}
args2 := redis.Args{}.AddFlat(p)
conn.Do(args2...)
返回值转换处理
- 接收单个返回值:通常情况下,如果只有一个返回值,可以直接处理。
例如:
reply, err := conn.Do("GET", "key")
if err != nil {
// 错误处理
}
- 接收多个不同类型的返回值:如果返回值包含多个不同类型的数据,可以使用 redis.Values
来接收并进行解析。然后根据具体的数据类型进行处理。
例如:
reply, err := redis.Values(conn.Do("HMGET", "user:1001", "name", "age", "email"))
if err != nil {
// 错误处理
}
var name string
var age int
var email string
_, err := redis.Scan(reply, &name, &age, &email)
if err != nil {
// 错误处理
}
- 接收单个结构体:如果返回的数据可以直接映射到一个结构体,则可以使用 redis.ScanStruct 来解析。
例如:
type User struct {
Name string
Age int
Email string
}
var user User
reply, err := redis.Values(conn.Do("HGETALL", "user:1001"))
if err != nil {
// 错误处理
}
err := redis.ScanStruct(reply, &user)
if err != nil {
// 错误处理
}
- 接收多个结构体:如果需要接收多个结构体,则可以使用 redis.ScanSlice 来解析。
例如:
type User struct {
Name string
Age int
Email string
}
var users []User
reply, err := redis.Values(conn.Do("ZRANGE", "users", 0, -1))
if err != nil {
// 错误处理
}
for _, v := range reply {
var user User
err := redis.ScanStruct(v.([]interface{}), &user)
if err != nil {
// 错误处理
}
users = append(users, user)
}