本文内容主要是对 go-redis 库官方文档部分内容的翻译,源文档地址:https://redis.uptrace.dev/guide/go-redis.html go-redis 库 Github 地址:https://github.com/go-redis/redis
Introduction
go-redis 是目前最常用的 golang 操作 redis 的库之一。
它支持:
- Redis 3 命令,除了
QUIT
、MONITOR
、SLOWLOG
、SYNC
- Automatic connection pooling with circuit breaker support
- 发布/订阅
- 事务
- 管道 和 TxPipeline
- Scripting
- Timeouts
- Redis 哨兵
- Redis 集群
- Cluster of Redis Servers without using cluster mode and Redis Sentinel
- Ring
- Instrumentation
- Cache friendly
- 限流
- 分布式锁
Getting Started
安装
go-redis 仅支持通过 Go modules 使用,所以首先你需要初始化一个 Go module:
$ go mod init github.com/my/repo
如果使用 Redis 6 及以下版本,安装 go-redis/v8:
$ go get github.com/go-redis/redis/v8
如果使用 Redis 7,安装 go-redis/v9:
$ go get github.com/go-redis/redis/v9
连接 Redis 服务器
两种方式
第一种:
import "github.com/go-redis/redis/v8"
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password set
DB: 0, // use default DB
})
另一种常用的方法是使用连接字符串:
opt, err := redis.ParseURL("redis://<user>:<pass>@localhost:6379/<db>")
if err != nil {
panic(err)
}
rdb := redis.NewClient(opt)
通过redis.NewClient
实际上会创建一个连接池,go-redis 会自动帮我们维护这个连接池。
上面的代码无法判断是否真的能连接成功,可以用以下代码验证:
// 验证连接
if err := client.Ping(context.Background()).Err(); err != nil {
panic("Redis 连接失败!\n" + err.Error())
}
:::info
当 go-redis 无法连接到 Redis 服务器时,会收到dial tcp: i/o timeout
错误消息,例如,当服务器关闭或端口受防火墙保护时。
:::
使用 TLS
要启用 TLS/SSL,需要提供一个空的tls.Config
。
如果使用的是私有证书,需要在tls.Config
中指定它们。
rdb := redis.NewClient(&redis.Options{
TLSConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
//Certificates: []tls.Certificate{cert}
},
})
如果得到x509: cannot validate certificate for xxx.xxx.xxx.xxx because it doesn't contain any IP SANs
错误,尝试设置ServerName
选项:
rdb := redis.NewClient(&redis.Options{
TLSConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
ServerName: "your.domain.com",
},
})
通过 SSH 通道连接
sshConfig := &ssh.ClientConfig{
User: "root",
Auth: []ssh.AuthMethod{ssh.Password("password")},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Timeout: 15 * time.Second,
}
sshClient, err := ssh.Dial("tcp", "remoteIP:22", sshConfig)
if err != nil {
panic(err)
}
rdb := redis.NewClient(&redis.Options{
Addr: net.JoinHostPort("127.0.0.1", "6379"),
Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) {
return sshClient.Dial(network, addr)
},
// Disable timeouts, because SSH does not support deadlines.
ReadTimeout: -1,
WriteTimeout: -1,
})
Context
每个 Redis 命令都接受一个上下文,可以使用它来设置超时或传播一些信息,例如跟踪上下文。
ctx := context.Background()
执行命令
可以这样执行一条命令:
val, err := rdb.Get(ctx, "key").Result()
fmt.Println(val)
或者,可以保存命令并稍后分别访问值和错误:
get := rdb.Get(ctx, "key")
fmt.Println(get.Val(), get.Err())
执行任意命令、自定义命令
val, err := rdb.Do(ctx, "get", "key").Result()
if err != nil {
if err == redis.Nil {
fmt.Println("key does not exists")
return
}
panic(err)
}
fmt.Println(val.(string))
Do
返回一个[Cmd](https://pkg.go.dev/github.com/go-redis/redis/v8#Cmd)
结构体,可以通过调用它的一些方法将值转换为各种类型:
// Text is a shortcut for get.Val().(string) with proper error handling.
val, err := rdb.Do(ctx, "get", "key").Text()
fmt.Println(val, err)
方法的完整列表:
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()
redis.Nil
如果 Redis 返回(nil)
(即 key 不存在),go-redis 会返回redis.Nil
错误。
在下面的示例中,我们使用redis.Nil
来区分空字符串回复和(nil)
回复(key 不存在):
val, err := rdb.Get(ctx, "key").Result()
switch {
case err == redis.Nil:
fmt.Println("key does not exist")
case err != nil:
fmt.Println("Get failed", err)
case val == "":
fmt.Println("value is empty")
}
GET
并不是唯一可能返回redis.Nil
的命令,如BLPOP
、ZSCORE
等也可能返回redis.Nil
。
Conn
Conn 表示单个 Redis 连接,而不是连接池。
除非特别需要连续的单个 Redis 连接,否则最好还是使用redis.NewClient
的方式。
cn := rdb.Conn(ctx)
defer cn.Close()
if err := cn.ClientSetName(ctx, "myclient").Err(); err != nil {
panic(err)
}
name, err := cn.ClientGetName(ctx).Result()
if err != nil {
panic(err)
}
fmt.Println("client name", name)
连接 Redis 集群
go-redis 自带 Redis Cluster 客户端。redis.ClusterClient
底层使用redis.Client
与集群中的每个节点进行通信。每个redis.Client
都维护一个单独的连接池。
连接 Redis 集群:
import "github.com/go-redis/redis/v8"
rdb := redis.NewClusterClient(&redis.ClusterOptions{
Addrs: []string{":7000", ":7001", ":7002", ":7003", ":7004", ":7005"},
// To route commands by latency or randomly, enable one of the following.
//RouteByLatency: true,
//RouteRandomly: true,
})
迭代分片:
err := rdb.ForEachShard(ctx, func(ctx context.Context, shard *redis.Client) error {
return shard.Ping(ctx).Err()
})
if err != nil {
panic(err)
}
要迭代主节点,使用ForEachMaster
;要迭代从节点,使用ForEachSlave
。
更改某些分片的选项:
rdb := redis.NewClusterClient(&redis.ClusterOptions{
NewClient: func(opt *redis.Options) *redis.NewClient {
user, pass := userPassForAddr(opt.Addr)
opt.Username = user
opt.Password = pass
return redis.NewClient(opt)
},
})
go-redis vs redigo
Feature | go-redis | redigo |
---|---|---|
GitHub stars | 14k+ | 9k+ |
类型安全 | ✔️ | ❌ |
连接池 | 自动的 | 手动的 |
自定义命令 | ✔️ | ✔️ |
高级 发布订阅 API | ✔️ | ❌ |
Redis 哨兵客户端 | ✔️ | 使用插件 |
Redis 集群客户端 | ✔️ | 使用插件 |
Redis Ring | ✔️ | ❌ |
OpenTelemetry instrumentation | ✔️ | ❌ |
两个项目的主要区别在于 go-redis 为每个 Redis 命令提供类型安全的 API,但 redigo 使用类似打印的 API:
// go-redis
timeout := time.Second
_, err := rdb.Set(ctx, "key", "value", timeout).Result()
// redigo
_, err := conn.Do("SET", "key", "value", "EX", 1)
当然,go-redis 也支持类似打印的 API:
// go-redis print-like API
ok, err := rdb.Do(ctx, "SET" "key", "value", "EX", 1).Bool()
此外,go-redis 自动使用连接池,但 redigo 需要显式管理连接:
// go-redis implicitly uses connection pooling
_, err := rdb.Set(...).Result()
// redigo requires explicit connection management
conn := pool.Get()
_, err := conn.Do(...)
conn.Close()
但是如果你需要手动管理连接,go-redis 也允许你这样做:
// go-redis manual connection management
conn := rdb.Conn(ctx)
_, err := conn.Set(...).Result()
conn.Close()