2. Go连接Redis:使用go-redis库建立连接、连接池配置、Ping测试、基础命令封装
在Go生态中,go-redis 是连接Redis的事实标准库,它提供了连接管理、连接池、哨兵/集群支持以及丰富的命令封装。本章将围绕 go-redis/redis/v9 展开,从安装、连接建立、连接池调优、Ping健康检查到基础命令的封装,构建一个可复用的Redis客户端层。
2.1 安装与导入
使用 go get 安装最新稳定版(v9 系列):
go get github.com/redis/go-redis/v9
在代码中导入:
import (
"context"
"fmt"
"time"
"github.com/redis/go-redis/v9"
)
2.2 建立连接:三种模式
go-redis 支持三种连接模式,分别对应不同的部署架构。下表总结了它们的适用场景与核心配置差异:
| 模式 | 适用场景 | 核心结构体 | 关键配置项 |
|---|---|---|---|
| 单节点 | 开发/测试、小规模生产 | redis.Client |
Addr, Password, DB |
| 哨兵 | 高可用自动故障转移 | redis.FailoverClient |
MasterName, SentinelAddrs |
| 集群 | 数据分片、大规模存储 | redis.ClusterClient |
Addrs (多个节点地址) |
单节点连接示例(最常用):
func NewRedisClient(addr, password string, db int) *redis.Client {
return redis.NewClient(&redis.Options{
Addr: addr, // "localhost:6379"
Password: password, // 无密码则留空
DB: db, // 默认 0
})
}
哨兵模式示例:
func NewSentinelClient(masterName string, sentinelAddrs []string, password string) *redis.FailoverClient {
return redis.NewFailoverClient(&redis.FailoverOptions{
MasterName: masterName,
SentinelAddrs: sentinelAddrs,
Password: password,
})
}
集群模式示例:
func NewClusterClient(addrs []string, password string) *redis.ClusterClient {
return redis.NewClusterClient(&redis.ClusterOptions{
Addrs: addrs,
Password: password,
})
}
2.3 连接池配置:参数与调优
go-redis 内部维护了一个 连接池,默认配置适用于大多数场景,但在高并发下需要手动调优。连接池的核心参数定义在 redis.Options 中:
| 参数 | 默认值 | 说明 | 调优建议 |
|---|---|---|---|
PoolSize |
10 * runtime.GOMAXPROCS(0) | 最大连接数 | 根据并发量调整,通常 50~200 |
MinIdleConns |
0 | 最小空闲连接数 | 建议设为 10~20,避免冷启动 |
MaxIdleConns |
PoolSize | 最大空闲连接数 | 通常与 PoolSize 一致 |
ConnMaxLifetime |
0(不限制) | 连接最大存活时间 | 建议 30~60 分钟,防止网络设备超时 |
ConnMaxIdleTime |
0(不限制) | 连接最大空闲时间 | 建议 5~10 分钟,释放闲置连接 |
DialTimeout |
5s | 建立连接超时 | 内网 1~3s,跨机房可适当增大 |
ReadTimeout |
3s | 读取超时 | 根据命令耗时调整 |
WriteTimeout |
3s | 写入超时 | 与 ReadTimeout 保持一致 |
生产级连接池配置示例:
func NewProdRedisClient() *redis.Client {
return redis.NewClient(&redis.Options{
Addr: "redis-prod:6379",
Password: "securepass",
DB: 0,
PoolSize: 100, // 最大连接数
MinIdleConns: 20, // 保持 20 个空闲连接
MaxIdleConns: 50, // 最大空闲连接数
ConnMaxLifetime: 30 * time.Minute, // 连接最多存活 30 分钟
ConnMaxIdleTime: 10 * time.Minute, // 空闲 10 分钟则关闭
DialTimeout: 3 * time.Second,
ReadTimeout: 3 * time.Second,
WriteTimeout: 3 * time.Second,
})
}
连接池调优原则: PoolSize 并非越大越好。过大会导致 Redis 端文件描述符耗尽,且增加上下文切换。建议根据
QPS × 平均命令耗时 估算,再留 20%~30% 余量。使用 redis.PoolStats() 监控命中率与等待数。
2.4 Ping 测试:健康检查
建立连接后,使用 Ping 命令验证网络与认证是否正常:
func PingTest(client *redis.Client) error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
pong, err := client.Ping(ctx).Result()
if err != nil {
return fmt.Errorf("redis ping failed: %w", err)
}
fmt.Println("Ping response:", pong) // 输出 "PONG"
return nil
}
常见 Ping 失败原因:
- 地址/端口错误
- 密码不匹配
- 防火墙或安全组未放行
- Redis 服务未启动
- 连接池耗尽导致超时
注意: 生产环境中,Ping 应作为 启动时的一次性检查,而非每次请求的前置操作。持续健康检查建议使用
redis.Conn 或独立的定时任务。
2.5 基础命令封装:构建通用操作层
直接使用 client.Get()、client.Set() 等原始方法虽然简单,但在大型项目中会导致代码重复。推荐封装一个 通用缓存接口,统一处理序列化、过期时间、错误包装等逻辑。
定义接口:
type Cache interface {
Get(ctx context.Context, key string) (string, error)
Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error
Del(ctx context.Context, keys ...string) error
Exists(ctx context.Context, key string) (bool, error)
Expire(ctx context.Context, key string, expiration time.Duration) error
Close() error
}
实现结构体:
type RedisCache struct {
client *redis.Client
}
func NewRedisCache(client *redis.Client) *RedisCache {
return &RedisCache{client: client}
}
func (r *RedisCache) Get(ctx context.Context, key string) (string, error) {
val, err := r.client.Get(ctx, key).Result()
if err == redis.Nil {
return "", nil // key 不存在,返回空字符串而非错误
}
return val, err
}
func (r *RedisCache) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error {
return r.client.Set(ctx, key, value, expiration).Err()
}
func (r *RedisCache) Del(ctx context.Context, keys ...string) error {
return r.client.Del(ctx, keys...).Err()
}
func (r *RedisCache) Exists(ctx context.Context, key string) (bool, error) {
n, err := r.client.Exists(ctx, key).Result()
return n > 0, err
}
func (r *RedisCache) Expire(ctx context.Context, key string, expiration time.Duration) error {
return r.client.Expire(ctx, key, expiration).Err()
}
func (r *RedisCache) Close() error {
return r.client.Close()
}
封装要点:
- 统一处理
redis.Nil:将“key不存在”与“其他错误”区分,避免上层误判。 - 序列化外置:接口只处理 string,序列化(JSON/Protobuf)由调用方或装饰器完成。
- 过期时间必传:强制调用方显式设置 TTL,防止遗忘导致内存泄漏。
2.6 连接与命令流程总览
下图展示了从客户端初始化到命令执行的完整链路,以及连接池在其中的角色:
2.7 完整初始化与使用示例
将上述模块组合,形成一个可运行的初始化流程:
func main() {
// 1. 创建客户端
client := NewProdRedisClient()
// 2. Ping 测试
if err := PingTest(client); err != nil {
panic(err)
}
// 3. 封装缓存层
cache := NewRedisCache(client)
// 4. 使用
ctx := context.Background()
cache.Set(ctx, "user:1001", `{"name":"Alice"}`, 10*time.Minute)
val, _ := cache.Get(ctx, "user:1001")
fmt.Println("cached value:", val)
// 5. 关闭
defer cache.Close()
}
最佳实践: 将
redis.Client 作为单例注入到依赖容器中,避免每次请求都创建新连接。连接池的统计信息可通过 client.PoolStats() 暴露给监控系统。
2.8 本章小结
- go-redis 支持 单节点、哨兵、集群 三种连接模式,按部署架构选择。
- 连接池参数
PoolSize、MinIdleConns、ConnMaxLifetime需根据并发量调优。 - Ping 用于启动时健康检查,不应作为每次请求的前置操作。
- 封装 Cache 接口 统一处理
redis.Nil、序列化、过期时间,提升代码可维护性。 - 连接池是性能的关键,通过
PoolStats()持续监控命中率与等待数。