必须显式调用client.Close(),否则连接池的TCP连接不会释放,长期运行导致fd耗尽、报too many open files;Set第三个参数须为time.Duration(如60*time.Second),非整数秒;Get返回redis.Nil需用errors.Is(err, redis.Nil)判断,非panic。

redis.NewClient 初始化后必须显式调用 client.Close(),否则长期运行的服务会因文件描述符(fd)耗尽而报 too many open files 或 dial tcp: lookup xxx: no such host。
redis.NewClient 后不 Close 会导致 fd 耗尽
Go 的 redis.NewClient 默认启用连接池,底层 TCP 连接不会随 goroutine 结束自动释放。进程生命周期内若从未调用 client.Close(),连接会持续堆积。
- HTTP server 类服务最典型:启动时初始化一次 client,但进程不退出,连接越积越多
- 错误表现不是 Redis 报错,而是系统级报错——
too many open files、DNS 解析失败等,日志里找不到 Redis 相关线索 - 别依赖 defer client.Close():它只在当前函数退出时触发,对常驻进程无效
- 正确姿势:
defer放在main()函数末尾,或在 HTTP server 的Shutdown()回调中调用
Set 第三个参数必须是 time.Duration,不是整数秒
client.Set(ctx, key, value, 60) 看似写对了,实则传的是 60 纳秒——Go 的 time.Duration 底层是 int64,单位是纳秒。直接传 60 就等于 60 * time.Nanosecond,缓存几乎立刻失效。
- ✅ 正确写法:
60 * time.Second、5 * time.Minute - ❌ 错误写法:
60(纳秒)、"60s"(类型不匹配,编译失败) - ⚠️ 特殊值:
0表示永不过期;-1在新版 go-redis/v9 中会被转为永不过期,但旧版本可能 panic,避免使用
Get 返回 redis.Nil 不是错误,要区分处理
client.Get(ctx, key).Result() 成功时返回值和 nil error;key 不存在时返回空字符串和 redis.Nil 错误——这不是异常,是预期行为。
立即学习“go语言免费学习笔记(深入)”;
- 别写
if err != nil { panic(err) },否则 key 不存在就崩了 - 正确判断:
if errors.Is(err, redis.Nil) { /* key 不存在 */ } - 别用
.Val():它会在 error 不为 nil 时直接 panic,.Result()才可控 - 结构体反序列化前,先检查
len(data) == 0,避免json.Unmarshal(nil, &v)静默失败
并发读写同一 key 时,GET + SET 不是原子操作
想实现“查缓存,没命中再查 DB 并回填”,直接 Get 后跟 Set 是竞态高发区。两个 goroutine 同时发现缓存 miss,都会去查 DB,然后都 Set,造成重复计算和写入放大。
- ✅ 用
client.SetNX(ctx, key, value, ttl)实现原子性“首次设置” - ✅ 更稳妥的缓存穿透防护:加锁 + 双检(先 Get,再 SetNX,再 Get)
- ⚠️ 注意:
SetNX本身不支持设过期时间(v9+ 的Set支持NX选项),老版本需组合SetNX + Expire,但这俩非原子,仍有小窗口竞态 - Hash 类型更适合结构化数据:
HSet局部更新不覆盖全量,但 TTL 仍作用于整个 key,无法给单个 field 设过期
生产环境最容易被忽略的是 context 超时和连接池关闭时机的配对——client.Close() 没调,问题延迟暴露;context.Background() 没设 timeout,goroutine 卡住却无日志可查。这两处不出问题则已,一出就是线上雪崩。


















