本文内容主要是对 go-redis 库官方文档部分内容的翻译,源文档地址:https://redis.uptrace.dev/guide/go-redis.html go-redis 库 Github 地址:https://github.com/go-redis/redis

Introduction

go-redis 是目前最常用的 golang 操作 redis 的库之一。

它支持:

Getting Started

安装

go-redis 仅支持通过 Go modules 使用,所以首先你需要初始化一个 Go module:

  1. $ go mod init github.com/my/repo

如果使用 Redis 6 及以下版本,安装 go-redis/v8

  1. $ go get github.com/go-redis/redis/v8

如果使用 Redis 7,安装 go-redis/v9

  1. $ go get github.com/go-redis/redis/v9

连接 Redis 服务器

两种方式

第一种:

  1. import "github.com/go-redis/redis/v8"
  2. rdb := redis.NewClient(&redis.Options{
  3. Addr: "localhost:6379",
  4. Password: "", // no password set
  5. DB: 0, // use default DB
  6. })

另一种常用的方法是使用连接字符串:

  1. opt, err := redis.ParseURL("redis://<user>:<pass>@localhost:6379/<db>")
  2. if err != nil {
  3. panic(err)
  4. }
  5. rdb := redis.NewClient(opt)

通过redis.NewClient实际上会创建一个连接池,go-redis 会自动帮我们维护这个连接池。

上面的代码无法判断是否真的能连接成功,可以用以下代码验证:

  1. // 验证连接
  2. if err := client.Ping(context.Background()).Err(); err != nil {
  3. panic("Redis 连接失败!\n" + err.Error())
  4. }

:::info 当 go-redis 无法连接到 Redis 服务器时,会收到dial tcp: i/o timeout错误消息,例如,当服务器关闭或端口受防火墙保护时。 :::

使用 TLS

要启用 TLS/SSL,需要提供一个空的tls.Config
如果使用的是私有证书,需要在tls.Config指定它们。

  1. rdb := redis.NewClient(&redis.Options{
  2. TLSConfig: &tls.Config{
  3. MinVersion: tls.VersionTLS12,
  4. //Certificates: []tls.Certificate{cert}
  5. },
  6. })

如果得到x509: cannot validate certificate for xxx.xxx.xxx.xxx because it doesn't contain any IP SANs错误,尝试设置ServerName选项:

  1. rdb := redis.NewClient(&redis.Options{
  2. TLSConfig: &tls.Config{
  3. MinVersion: tls.VersionTLS12,
  4. ServerName: "your.domain.com",
  5. },
  6. })

通过 SSH 通道连接

  1. sshConfig := &ssh.ClientConfig{
  2. User: "root",
  3. Auth: []ssh.AuthMethod{ssh.Password("password")},
  4. HostKeyCallback: ssh.InsecureIgnoreHostKey(),
  5. Timeout: 15 * time.Second,
  6. }
  7. sshClient, err := ssh.Dial("tcp", "remoteIP:22", sshConfig)
  8. if err != nil {
  9. panic(err)
  10. }
  11. rdb := redis.NewClient(&redis.Options{
  12. Addr: net.JoinHostPort("127.0.0.1", "6379"),
  13. Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) {
  14. return sshClient.Dial(network, addr)
  15. },
  16. // Disable timeouts, because SSH does not support deadlines.
  17. ReadTimeout: -1,
  18. WriteTimeout: -1,
  19. })

Context

每个 Redis 命令都接受一个上下文,可以使用它来设置超时或传播一些信息,例如跟踪上下文

  1. ctx := context.Background()

执行命令

可以这样执行一条命令:

  1. val, err := rdb.Get(ctx, "key").Result()
  2. fmt.Println(val)

或者,可以保存命令并稍后分别访问值和错误:

  1. get := rdb.Get(ctx, "key")
  2. fmt.Println(get.Val(), get.Err())

执行任意命令、自定义命令

  1. val, err := rdb.Do(ctx, "get", "key").Result()
  2. if err != nil {
  3. if err == redis.Nil {
  4. fmt.Println("key does not exists")
  5. return
  6. }
  7. panic(err)
  8. }
  9. fmt.Println(val.(string))

Do返回一个[Cmd](https://pkg.go.dev/github.com/go-redis/redis/v8#Cmd)结构体,可以通过调用它的一些方法将值转换为各种类型:

  1. // Text is a shortcut for get.Val().(string) with proper error handling.
  2. val, err := rdb.Do(ctx, "get", "key").Text()
  3. fmt.Println(val, err)

方法的完整列表:

  1. s, err := cmd.Text()
  2. flag, err := cmd.Bool()
  3. num, err := cmd.Int()
  4. num, err := cmd.Int64()
  5. num, err := cmd.Uint64()
  6. num, err := cmd.Float32()
  7. num, err := cmd.Float64()
  8. ss, err := cmd.StringSlice()
  9. ns, err := cmd.Int64Slice()
  10. ns, err := cmd.Uint64Slice()
  11. fs, err := cmd.Float32Slice()
  12. fs, err := cmd.Float64Slice()
  13. bs, err := cmd.BoolSlice()

redis.Nil

如果 Redis 返回(nil)(即 key 不存在),go-redis 会返回redis.Nil错误。

在下面的示例中,我们使用redis.Nil来区分空字符串回复和(nil)回复(key 不存在):

  1. val, err := rdb.Get(ctx, "key").Result()
  2. switch {
  3. case err == redis.Nil:
  4. fmt.Println("key does not exist")
  5. case err != nil:
  6. fmt.Println("Get failed", err)
  7. case val == "":
  8. fmt.Println("value is empty")
  9. }

GET并不是唯一可能返回redis.Nil的命令,如BLPOPZSCORE等也可能返回redis.Nil

Conn

Conn 表示单个 Redis 连接,而不是连接池。
除非特别需要连续的单个 Redis 连接,否则最好还是使用redis.NewClient的方式。

  1. cn := rdb.Conn(ctx)
  2. defer cn.Close()
  3. if err := cn.ClientSetName(ctx, "myclient").Err(); err != nil {
  4. panic(err)
  5. }
  6. name, err := cn.ClientGetName(ctx).Result()
  7. if err != nil {
  8. panic(err)
  9. }
  10. fmt.Println("client name", name)

连接 Redis 集群

go-redis 自带 Redis Cluster 客户端redis.ClusterClient底层使用redis.Client与集群中的每个节点进行通信。每个redis.Client都维护一个单独的连接池。

连接 Redis 集群:

  1. import "github.com/go-redis/redis/v8"
  2. rdb := redis.NewClusterClient(&redis.ClusterOptions{
  3. Addrs: []string{":7000", ":7001", ":7002", ":7003", ":7004", ":7005"},
  4. // To route commands by latency or randomly, enable one of the following.
  5. //RouteByLatency: true,
  6. //RouteRandomly: true,
  7. })

迭代分片:

  1. err := rdb.ForEachShard(ctx, func(ctx context.Context, shard *redis.Client) error {
  2. return shard.Ping(ctx).Err()
  3. })
  4. if err != nil {
  5. panic(err)
  6. }

要迭代主节点,使用ForEachMaster;要迭代从节点,使用ForEachSlave

更改某些分片的选项:

  1. rdb := redis.NewClusterClient(&redis.ClusterOptions{
  2. NewClient: func(opt *redis.Options) *redis.NewClient {
  3. user, pass := userPassForAddr(opt.Addr)
  4. opt.Username = user
  5. opt.Password = pass
  6. return redis.NewClient(opt)
  7. },
  8. })

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:

  1. // go-redis
  2. timeout := time.Second
  3. _, err := rdb.Set(ctx, "key", "value", timeout).Result()
  4. // redigo
  5. _, err := conn.Do("SET", "key", "value", "EX", 1)

当然,go-redis 也支持类似打印的 API:

  1. // go-redis print-like API
  2. ok, err := rdb.Do(ctx, "SET" "key", "value", "EX", 1).Bool()

此外,go-redis 自动使用连接池,但 redigo 需要显式管理连接:

  1. // go-redis implicitly uses connection pooling
  2. _, err := rdb.Set(...).Result()
  3. // redigo requires explicit connection management
  4. conn := pool.Get()
  5. _, err := conn.Do(...)
  6. conn.Close()

但是如果你需要手动管理连接,go-redis 也允许你这样做:

  1. // go-redis manual connection management
  2. conn := rdb.Conn(ctx)
  3. _, err := conn.Set(...).Result()
  4. conn.Close()