Go项目Redis缓存核心是连得稳、写得对、读得准、崩得少,关键在初始化用redis.NewClient()、显式配置Options、分层设置超时、检查redis.Nil错误、实现客户端穿透雪崩防护。

Go 项目里用 Redis 做缓存,核心不是“能不能连上”,而是“连得稳、写得对、读得准、崩得少”——多数问题出在初始化时机、连接复用和超时配置上,不是代码写错,是配置没压住边界。
redis.Client 初始化必须用 redis.NewClient(),别用 redis.Dial()
旧式 redis.Dial()(来自老版本 redigo)已不适用 Go modules 项目,且不支持 context 取消、自动重连、连接池管理。当前主流 redis 客户端是 github.com/redis/go-redis/v9,它要求显式构造 redis.Client 实例:
- 必须传入
redis.Options{Addr: "localhost:6379", Password: "", DB: 0},DB不填默认是 0,但建议显式写出来,避免跨环境误用 -
Context不参与初始化,但所有操作(Set、Get)都需传入,超时控制全靠它 - 连接池由客户端内部管理,
PoolSize默认 10,高并发服务建议设为50~100,但别盲目调大,超过 Redis 的maxclients会拒绝新连接
超时设置必须分三层:DialTimeout、ReadTimeout、WriteTimeout
只设 Context.WithTimeout 不够——网络卡在建连阶段时,context 还没生效。真正起作用的是底层连接参数:
-
DialTimeout: 5 * time.Second:控制 TCP 握手+认证耗时上限 -
ReadTimeout: 3 * time.Second:读响应的单次等待时间(GET/SET 返回前) -
WriteTimeout: 3 * time.Second:发命令到缓冲区的上限,非服务器处理时间 - 三者必须全部设置,否则默认为 0(无限等待),线上偶发 hang 死就源于此
GET 返回 *redis.StringCmd,.Val() 前务必检查 .Err()
Redis 命令返回的不是原始值,而是封装了状态的命令对象。常见错误是直接 cmd.Val() 而忽略错误分支:
立即学习“go语言免费学习笔记(深入)”;
val, err := rdb.Get(ctx, "user:123").Result()
if err == redis.Nil {
// key 不存在 —— 这是合法状态,不是 error
} else if err != nil {
// 网络失败、序列化错误等真异常
log.Printf("redis GET failed: %v", err)
return
}
// 此时 val 才是 string 类型值
-
redis.Nil是预定义错误,表示 key 不存在,业务常需特殊处理(比如回源加载) - 不要用
err != nil一刀切判断失败,redis.Nil属于“预期中的空” - 如果用
.Val()而不先.Result(),可能 panic:未检查 error 就取值
缓存穿透/雪崩防护不能只靠 TTL,要结合 client-side fallback
单纯给 Set 加 WithArgs(redis.Expiration, 30*time.Second) 挡不住穿透或雪崩。真实压力来自:大量请求击穿缓存查 DB 或 大批 key 同时过期导致 DB 瞬时压垮:
- 穿透防护:GET 返回
redis.Nil时,改用SET key "" EX 60 NX占位(空值缓存),防止重复打 DB - 雪崩防护:TTL 避免硬编码,用
rand.Int63n(30) + 30动态加扰动,让过期时间分散 - 更关键的是:client 端加简单熔断(如 10 秒内 5 次 Redis timeout 就跳过缓存直连 DB),避免缓存层故障拖垮整个链路
这些逻辑没法靠配置打开,得写进业务调用封装里——Redis 客户端本身不提供穿透/雪崩策略,那是你代码的责任。










