go-redis 的 ctx 必须设超时,禁用 context.background();读写操作均需 withtimeout;set 覆盖写,setnx 实现分布式锁;json 存取需导出字段+json tag;scan 需循环游标、调大 count,集群下不跨节点。

go-redis 的 ctx 不能用 context.Background() 直接传
线上服务一卡就是几秒甚至 goroutine 堆积,八成是这里没设超时。context.Background() 没有 deadline、不可取消,一旦 Redis 响应慢或网络抖动,Get、Set 就会无限阻塞当前 goroutine。
- 读操作统一用
context.WithTimeout(r.Context(), 300*time.Millisecond)(HTTP handler 中优先复用r.Context()) - 写操作也别漏——尤其
SetNX或Expire这类依赖原子性的调用,超时缺失会导致锁逻辑假死 - 连接池耗尽时,没设 timeout 的请求会在
pool.Get阶段卡住,日志里根本看不到 Redis 错误,只看到 goroutine 数飙升 - 别在全局变量里存
ctx = context.Background(),它不是“默认上下文”,而是“无约束上下文”
Set 和 SetNX 到底该用哪个?
Set 是覆盖写,SetNX 是“仅当 key 不存在时才设值”,语义完全不同。很多人以为 Set(key, val, time.Hour) 就能防并发写,其实完全不能。
-
Set:不管 key 存不存在,都强行更新值和 TTL;适合普通缓存刷新 -
SetNX:返回bool,true表示抢锁成功,false表示 key 已存在;是分布式锁的基石 - v9+ 支持
client.Set(ctx, key, val, opts).WithArgs("NX", "EX", "30")原子设值+过期,但老版本必须SetNX+Expire组合——注意这两步不原子,有竞态风险 - 多数业务场景不需要强互斥,盲目用
SetNX反而增加延迟和失败率
JSON 存取总反序列化失败?先看字段导出和 tag
Redis 不管你存的是什么,它只存字节。json.Marshal 出来是空对象 {},大概率是结构体字段没导出或缺 json: tag。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 结构体字段首字母必须大写(可导出),且显式加 tag:
Name string `json:"name"` - 反序列化前务必检查原始数据:
if len(data) == 0 { return errors.New("empty redis value") } - 更稳妥的做法是封装
GetJSON(ctx, key, &v),内部统一处理redis.Nil、空字节、json.Unmarshal错误 - 别信日志里打印的
val,用redis-cli GET key看原始值,确认是不是真存了 JSON 字符串,还是存成了"&{...}"这种 Go 字符串表示
Scan 扫不出 key?不是命令错,是用法和集群限制
Scan 默认每次只返回 10 个 key,扫 10 万个 key 要发上万次请求,延迟高、吞吐低,还容易被 Redis 单线程拖垮。
- 显式加大
Count参数:client.Scan(ctx, cursor, "user:*", 1000).Val() - 游标必须循环使用,不能每次重置为 0;结束条件是返回的
cursor == 0 - Redis 集群模式下,
Scan只扫当前节点,不跨 slot;想全量枚举得自己遍历所有 master 节点连单机 client 扫 - 生产环境慎用
Scan,尤其高频触发;更适合用带哈希标签的 key(如{user1001}.profile)配合业务逻辑分片查
缓存不是加个 Set 就完事,真正难的是上下文透传、错误分类处理、空值兜底和连接池水位控制。很多问题现场看不出,等压测或上线后才爆发——比如 MinIdleConns 没设,突发流量来时冷启动慢;又比如 MaxConnAge 没配,长连接被中间设备静默断开却不重连。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










