Redis Set() 过期时间单位是纳秒,传60会变成60纳秒而非60秒,应使用time.Second*60;需显式Ping()验证连接;结构体JSON序列化要慎用omitempty;并发操作须为每次请求创建独立context。

redis.Client.Set() 的第三个参数别乱传 time.Duration
Go 用 redis.Client 写缓存时,Set() 第三个参数是过期时间,类型是 time.Duration。很多人直接传 60 或 3600,结果缓存永不过期——因为单位是纳秒,不是秒。
常见错误现象:redis-cli TTL key 返回 -1(永久)或 -2(key 不存在),但代码里明明写了“60秒”。
- 正确写法:用
time.Second * 60、time.Minute等标准常量,别手算纳秒 - 如果传
0,表示不设置过期;传-1会报错redis: nil duration - 注意
context.WithTimeout里的超时和缓存过期是两回事,别混在一起设
用 redis.NewClient() 时必须显式调用 client.Ping()
初始化 redis.Client 后,连接不会立刻建立,Ping() 才真正触发底层拨号和认证。很多程序上线后缓存写入失败,日志只显示 redis: connection closed,其实是因为没连上就直接 Set() 了。
- 在
main()初始化完 client 后,加一行if err := client.Ping(ctx).Err(); err != nil { log.Fatal(err) } - 别依赖 defer client.Close() 前的任意操作来“顺带”验证连接
- Docker 或 K8s 环境下,网络就绪慢,
Ping()失败时建议重试 3 次,每次间隔 500ms
结构体存 Redis 要小心 json.Marshal 的零值处理
Go 把结构体 Set() 到 Redis,通常走 json.Marshal()。但默认序列化会把空字符串 ""、零值 0、false 全部写进去,而业务逻辑可能靠字段是否为零来判断“未填写”,导致缓存和 DB 不一致。
立即学习“go语言免费学习笔记(深入)”;
- 给结构体字段加
json:",omitempty"标签,但注意:它对指针类型无效,*string为 nil 时才跳过 - 如果字段是
string类型且允许为空,建议统一用*string,避免误判 -
json.Marshal(nil)返回null字符串,Redis 里存的是"null",取出来再Unmarshal会报错invalid character 'n' looking for beginning of value
并发 Set/Get 时别复用同一个 context.Context
一个 context.Context 被多个 goroutine 同时传给 client.Get() 或 client.Set(),看似省事,实则危险:一旦某个请求提前超时或取消,整个 context 就被 cancel,其他还在跑的请求也会立刻中断,出现大量 context canceled 错误。
- 每个 Redis 操作都该用独立的 context,比如
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) - 不要把 context 存成全局变量或结构体字段长期持有
- HTTP handler 里用
r.Context()是安全的,因为它天然 per-request;但后台定时任务必须自己新建
缓存逻辑越简单越好,但初始化、序列化、上下文这三块最容易漏掉细节,一漏就是线上超时或脏数据。


















