1. 简介
github仓库地址:https://github.com/redis/go-redis
文档地址:https://pkg.go.dev/github.com/redis/go-redis/v9
go-redis 是 Go 语言的 Redis 客户端。
2. 使用
2.1 安装
使用 go get 将 go-redis 包下载到 GOPATH 指定的目录下。
go get github.com/redis/go-redis/v9
2.2 连接
通过 redis.NewClient 设置配置连接 Redis。
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password set
DB: 0, // use default DB
})
也可以通过 Redis URI 来连接。
// 指定用户名密码
url := "redis://user:password@localhost:6379/0?"
opts, err := redis.ParseURL(url)
if err != nil {
panic(err)
}
rdb := redis.NewClient(opts)
// 不指定用户名密码
url := "redis://@localhost:6379/0?"
opts, err := redis.ParseURL(url)
if err != nil {
panic(err)
}
rdb := redis.NewClient(opts)
2.3 执行命令
连接后返回的 *redis.Client 对象包含各种方法,对应 Redis 的不同命令,通过调用它的方法来执行 Redis 命令。
// set 命令
err := rdb.Set(ctx, "key", "value", 0).Err()
// get 命令
val, err := rdb.Get(ctx, "a").Result()
Do 方法可以执行任意命令,包括 Redis 支持了而 go-redis 客户端仍未支持的命令。
val, err := rdb.Do(ctx, "get", "key").Result()
val.(string)
// Text 方法相当于 get.Val().(string),直接获取字符串类型结果
val, err := rdb.Do(ctx, "get", "key").Text()
Do 方法返回类型为 *redis.Cmd,返回变量可以转换为其它类型。
s, err := cmd.Text()
flag, err := cmd.Bool()
num, err := cmd.Int()
num, err := cmd.Int64()
num, err := cmd.Uint64()
num, err := cmd.Float32()
num, err := cmd.Float64()
ss, err := cmd.StringSlice()
ns, err := cmd.Int64Slice()
ns, err := cmd.Uint64Slice()
fs, err := cmd.Float32Slice()
fs, err := cmd.Float64Slice()
bs, err := cmd.BoolSlice()
2.4 Redis功能
集群
go-redis 支持 Redis Cluster 客户端。
redis.ClusterClient 表示集群对象,对集群内每个 redis 节点使用 redis.Client 对象进行通信,每个 redis.Client 会拥有单独的连接池。
rdb := redis.NewClusterClient(&redis.ClusterOptions{
Addrs: []string{":7000", ":7001", ":7002", ":7003", ":7004", ":7005"},
})
ForEachShard 方法遍历节点,ForEachMaster 方法遍历主节点,ForEachSlave 方法遍历从节点。
err := rdb.ForEachShard(ctx, func(ctx context.Context, shard *redis.Client) error {
return shard.Ping(ctx).Err()
})
哨兵
连接到哨兵模式管理的服务器。
rdb := redis.NewFailoverClient(&redis.FailoverOptions{
MasterName: "master-name",
SentinelAddrs: []string{":9126", ":9127", ":9128"},
})
连接到哨兵服务器。
sentinel := redis.NewSentinelClient(&redis.Options{
Addr: ":9126",
})
// 获取哨兵管理的服务器信息
addr, err := sentinel.GetMasterAddrByName(ctx, "master-name").Result()
分片
创建一个由三个节点组成的 Ring 客户端。
rdb := redis.NewRing(&redis.RingOptions{
Addrs: map[string]string{
// shardName => host:port
"shard1": "localhost:7000",
"shard2": "localhost:7001",
"shard3": "localhost:7002",
},
})
遍历节点。
err := rdb.ForEachShard(ctx, func(ctx context.Context, shard *redis.Client) error {
return shard.Ping(ctx).Err()
})
管道
通过 Pipeline 一次执行多个命令并读取返回值,需要调用 Exec 方法后获取返回值。
pipe := rdb.Pipeline()
incr := pipe.Incr(ctx, "pipeline_counter")
pipe.Expire(ctx, "pipeline_counter", time.Hour)
cmds, err := pipe.Exec(ctx)
if err != nil {
panic(err)
}
fmt.Println(incr.Val())
也可以使用 Pipelined 方法,它将自动调用 Exec 方法。
var incr *redis.IntCmd
cmds, err := rdb.Pipelined(ctx, func(pipe redis.Pipeliner) error {
incr = pipe.Incr(ctx, "pipelined_counter")
pipe.Expire(ctx, "pipelined_counter", time.Hour)
return nil
})
fmt.Println(incr.Val())
调用多个命令时遍历结果集获取结果。
cmds, err := rdb.Pipelined(ctx, func(pipe redis.Pipeliner) error {
for i := 0; i < 100; i++ {
pipe.Get(ctx, fmt.Sprintf("key%d", i))
}
return nil
})
for _, cmd := range cmds {
fmt.Println(cmd.(*redis.StringCmd).Val())
}
事务
使用 Watch 和事务管道,来实现 INCR 操作。
WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC
实现代码:
const maxRetries = 1000
// increment 方法,使用 GET + SET + WATCH 来实现Key递增效果,类似命令 INCR
func increment(key string) error {
// 事务函数
txf := func(tx *redis.Tx) error {
n, err := tx.Get(ctx, key).Int()
if err != nil && err != redis.Nil {
return err
}
n++
_, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
pipe.Set(ctx, key, n, 0)
return nil
})
return err
}
for i := 0; i < maxRetries; i++ {
err := rdb.Watch(ctx, txf, key)
if err == nil { // Success.
return nil
}
if err == redis.TxFailedErr { // 乐观锁失败
continue
}
return err
}
return errors.New("increment reached maximum number of retries")
}
发布订阅
发布一条消息。
err := rdb.Publish(ctx, "mychannel1", "payload").Err()
订阅一个 Channel,使用完后必须关闭它。
pubsub := rdb.Subscribe(ctx, "mychannel1")
defer pubsub.Close()
读取消息。
for {
msg, err := pubsub.ReceiveMessage(ctx)
if err != nil {
panic(err)
}
fmt.Println(msg.Channel, msg.Payload)
}
或者以 Go 通道的方式读取。
ch := pubsub.Channel()
for msg := range ch {
fmt.Println(msg.Channel, msg.Payload)
}
2.5 其它
redis.Nil
redis.Nil 是一种特殊的错误,表示 Redis 的一种状态。如使用 get 命令获取不存在的 key 的值时,返回 redis.Nil。
在实际逻辑中判断:
val, err := rdb.Get(ctx, "key").Result()
switch {
case err == redis.Nil:
fmt.Println("key不存在")
case err != nil:
fmt.Println("错误", err)
case val == "":
fmt.Println("值是空字符串")
}
redis.Conn
redis.Conn 是从连接池取出来的单个连接。
除非有特殊要求,否则尽量不要使用它。使用完之后应该调用 Close 方法将其返回给 go-redis,否则连接池会永远丢失一个连接。
cn := rdb.Conn(ctx)
defer cn.Close()