3. Go 连接 Redis:使用 go-redis 库建立连接、连接池配置、基础 CRUD 操作
在 Go 生态中,go-redis 是连接 Redis 的事实标准库。本章将围绕连接建立、连接池调优以及基础 CRUD 操作展开,确保你能够在业务接口中稳定、高效地使用 Redis。
3.1 安装与导入
使用 go get 安装最新版本:
go get github.com/redis/go-redis/v9
在代码中导入:
import (
"context"
"github.com/redis/go-redis/v9"
)
版本提示: v9 版本要求 Go 1.18+,并全面支持 Redis 7.x 的新特性(如 RESP3、ACL、Sharded Pub/Sub)。建议生产环境使用 v9 系列。
3.2 建立连接:单节点模式
最常用的连接方式是通过 redis.NewClient 创建单节点客户端:
var rdb *redis.Client
func InitRedis() error {
rdb = redis.NewClient(&redis.Options{
Addr: "localhost:6379", // Redis 地址
Password: "", // 密码,无则留空
DB: 0, // 默认数据库 0
PoolSize: 10, // 连接池大小
MinIdleConns: 5, // 最小空闲连接数
DialTimeout: 5 * time.Second, // 连接超时
ReadTimeout: 3 * time.Second, // 读取超时
WriteTimeout: 3 * time.Second, // 写入超时
})
// 验证连接
ctx := context.Background()
_, err := rdb.Ping(ctx).Result()
if err != nil {
return fmt.Errorf("redis ping failed: %w", err)
}
return nil
}
关键点:
Ping 是连接验证的标准方式。如果返回 pong 且无错误,说明连接已成功建立。
3.3 连接池配置详解
go-redis 内置了基于 pool 包的连接池。合理的连接池配置能显著提升吞吐量并降低延迟。下表列出核心参数及其推荐值:
| 参数 | 类型 | 默认值 | 推荐值 | 说明 |
|---|---|---|---|---|
PoolSize |
int | 10 | CPU 核心数 × 2 ~ 4 | 最大连接数,超过此值会等待 |
MinIdleConns |
int | 0 | 5 ~ 10 | 最小空闲连接,避免冷启动 |
MaxIdleConns |
int | PoolSize | 与 PoolSize 相同 | 最大空闲连接数 |
ConnMaxLifetime |
time.Duration | 0(不限制) | 30 分钟 ~ 1 小时 | 连接最大存活时间,防止长时间连接被 Redis 断开 |
ConnMaxIdleTime |
time.Duration | 0(不限制) | 5 分钟 | 空闲连接超时回收 |
DialTimeout |
time.Duration | 5s | 5s | 建立 TCP 连接超时 |
ReadTimeout |
time.Duration | 3s | 3s | 读取 Redis 响应超时 |
WriteTimeout |
time.Duration | 3s | 3s | 写入 Redis 命令超时 |
避坑指南: 不要将
PoolSize 设置过大(如超过 100),否则 Redis 端会因大量空闲连接而消耗内存。同时,ConnMaxLifetime 建议设置,避免因网络中间件(如 Nginx、云负载均衡)断开长时间空闲连接导致 broken pipe 错误。
3.4 连接池状态监控
go-redis 提供了 PoolStats() 方法,用于实时查看连接池状态:
stats := rdb.PoolStats()
fmt.Printf("连接池状态:\n")
fmt.Printf(" TotalConns: %d\n", stats.TotalConns) // 总连接数
fmt.Printf(" IdleConns: %d\n", stats.IdleConns) // 空闲连接数
fmt.Printf(" Hits: %d\n", stats.Hits) // 命中次数(从池中获取)
fmt.Printf(" Misses: %d\n", stats.Misses) // 未命中次数(新建连接)
fmt.Printf(" Timeouts: %d\n", stats.Timeouts) // 等待超时次数
通过监控这些指标,可以判断连接池配置是否合理:
- Hits 远大于 Misses:连接复用率高,配置良好。
- Timeouts 持续增长:PoolSize 过小或请求并发过高,需要扩容。
- IdleConns 长期接近 TotalConns:连接池过大,浪费资源。
3.5 基础 CRUD 操作
以下演示最常用的五种数据类型操作:String、Hash、List、Set、Sorted Set。
3.5.1 String 操作
ctx := context.Background()
// Set
err := rdb.Set(ctx, "user:1001:name", "张三", 0).Err()
if err != nil {
panic(err)
}
// Get
val, err := rdb.Get(ctx, "user:1001:name").Result()
if err == redis.Nil {
fmt.Println("key 不存在")
} else if err != nil {
panic(err)
} else {
fmt.Println("name:", val)
}
// 带过期时间的 Set
rdb.Set(ctx, "session:token:abc123", "data", 30*time.Minute)
// 批量操作
rdb.MSet(ctx, "key1", "val1", "key2", "val2")
vals, _ := rdb.MGet(ctx, "key1", "key2").Result()
3.5.2 Hash 操作
// HSet
rdb.HSet(ctx, "user:1001", map[string]interface{}{
"name": "张三",
"age": 28,
"city": "北京",
})
// HGet
name, _ := rdb.HGet(ctx, "user:1001", "name").Result()
// HGetAll
userMap, _ := rdb.HGetAll(ctx, "user:1001").Result()
fmt.Println(userMap) // map[age:28 city:北京 name:张三]
// HIncrBy
rdb.HIncrBy(ctx, "user:1001", "age", 1)
3.5.3 List 操作
// LPush / RPush
rdb.LPush(ctx, "queue:task", "task1", "task2")
rdb.RPush(ctx, "queue:task", "task3")
// LPop / RPop
task, _ := rdb.LPop(ctx, "queue:task").Result()
// LRange
tasks, _ := rdb.LRange(ctx, "queue:task", 0, -1).Result()
// LLen
length, _ := rdb.LLen(ctx, "queue:task").Result()
3.5.4 Set 操作
// SAdd
rdb.SAdd(ctx, "set:tags", "golang", "redis", "cache")
// SMembers
members, _ := rdb.SMembers(ctx, "set:tags").Result()
// SIsMember
exists, _ := rdb.SIsMember(ctx, "set:tags", "golang").Result()
// SRem
rdb.SRem(ctx, "set:tags", "cache")
3.5.5 Sorted Set 操作
// ZAdd
rdb.ZAdd(ctx, "leaderboard", redis.Z{
Score: 100,
Member: "player1",
}, redis.Z{
Score: 200,
Member: "player2",
})
// ZRangeByScore
opts := &redis.ZRangeBy{
Min: "0",
Max: "150",
}
results, _ := rdb.ZRangeByScoreWithScores(ctx, "leaderboard", opts).Result()
for _, z := range results {
fmt.Printf("Member: %s, Score: %f\n", z.Member, z.Score)
}
// ZIncrBy
rdb.ZIncrBy(ctx, "leaderboard", 50, "player1")
最佳实践: 对于业务接口中的 CRUD,建议将 Redis 操作封装为独立的 Repository 层,统一处理 key 前缀、序列化(如 JSON/Protobuf)、错误转换和重试逻辑,避免业务代码直接调用 go-redis 原始 API。
3.6 连接与操作流程总览
下图展示了从 Go 应用初始化到执行 CRUD 的完整链路:
3.7 连接池与 CRUD 交互时序
以下时序图展示了一次完整的 Get 操作在连接池中的流转过程:
3.8 常见错误与处理
| 错误类型 | 典型原因 | 处理方式 |
|---|---|---|
redis.Nil |
key 不存在 | 使用 errors.Is(err, redis.Nil) 判断,不要视为异常 |
dial tcp: i/o timeout |
Redis 不可达或网络延迟 | 检查网络、防火墙;增加 DialTimeout |
connection pool exhausted |
连接池满,请求排队超时 | 增大 PoolSize 或优化业务并发 |
broken pipe |
连接被 Redis 或中间件断开 | 设置 ConnMaxLifetime 定期回收连接 |
MOVED / ASK |
Redis 集群模式下 slot 迁移 | 使用 redis.NewClusterClient 自动处理 |
生产建议: 在业务接口中,建议对 Redis 操作添加 重试机制(如指数退避重试 3 次),并配合 熔断器(如
gobreaker)防止 Redis 故障时雪崩。同时,所有 Redis 操作应设置合理的 Context 超时,避免 Goroutine 泄漏。