用 redis.Client 连集群或哨兵,而非单节点;哨兵用 NewFailoverClient 并传 MasterName 和全部哨兵地址,集群用 NewClusterClient 并设 Timeout 与 MaxRetries。

用 redis.Client 连集群还是单节点?别硬套文档示例
Go 里最常踩的坑是:一上来就照抄 redis.NewClient 连单机,结果上线后缓存雪崩或超时频发——因为生产环境基本都是 Redis 集群或哨兵。单节点客户端不自动重试、不感知故障转移,redis.FailoverOptions 或 redis.ClusterOptions 才是真入口。
- 哨兵场景用
redis.NewFailoverClient,必须显式传MasterName和哨兵地址列表,漏掉任意一个哨兵地址会导致初始化失败 - 集群模式用
redis.NewClusterClient,节点列表只需填几个(比如 2–3 个),它会自动发现其余节点;但首次连接任一节点失败,整个 client 就 panic,得包一层if err != nil - 所有连接都建议设
Timeout: 3 * time.Second和MaxRetries: 2,否则网络抖动时请求直接卡死在ctx.Done()
Set 和 SetNX 性能差多少?看是否带过期时间
很多人以为 SetNX 天然比 Set 慢,其实关键在参数:如果 Set 带了 EX 或 PX,底层走的是 SET key value EX seconds 单命令;而 SetNX 默认不设过期,真要原子性“不存在才写+带过期”,得用 Set 的 XX/NX 选项组合。
-
client.Set(ctx, "k", "v", 30*time.Second)→ 单次 RTT,快 -
client.SetNX(ctx, "k", "v", 0)→ 不设过期,后续还得Expire,两次 RTT,且非原子 - 真正需要“存在不覆盖+带过期”时,用
client.Set(ctx, "k", "v", redis.SetArgs{NX: true, Ex: 30})
结构体存 json.Marshal 还是 gob?先看跨语言需求
Go 缓存结构体时,json.Marshal 是默认选择,但它有隐性成本:字段名转小写、空值被忽略、浮点精度丢失;gob 快且保真,但只限 Go 进程间通信——一旦下游是 Python 或 Node.js 服务,反序列化直接报错。
- 纯 Go 微服务内部:用
gob,序列化快 40%,内存占用低,但注意gob不兼容结构体字段增删(版本升级需加gob.Register) - 多语言或对外 API 场景:必须用
json,且结构体字段加json:"field_name,omitempty"控制输出,避免空字符串/零值污染缓存 - 别把
time.Time直接塞进去——json默认转 RFC3339 字符串,gob存纳秒整数,两边解析逻辑不一致,统一转成int64时间戳再存
本地缓存 + Redis 双写,怎么防 cache stampede?
本地缓存(比如 freecache)和 Redis 联用时,热点 key 过期瞬间大量请求穿透到 DB,不是靠加锁就能解决的——锁只拦住同进程请求,跨实例照样打穿 DB。
立即学习“go语言免费学习笔记(深入)”;
- 用
redis.Set(ctx, key, val, redis.SetArgs{NX: true, Ex: 5 * time.Second})抢占一个短过期“占位锁”,抢到的进程去查 DB 并回填两级缓存,没抢到的 sleep 后重试(别自旋) - 本地缓存本身要设比 Redis 更长的 TTL(比如 Redis 30s,本地 60s),并开启
OnEvicted回调,在淘汰前异步刷新 Redis,降低集中过期概率 - 绝对不要在
Get失败后直接Set空值——空结果也要带短 TTL(如 1s),否则缓存穿透变缓存污染
本地缓存和分布式缓存的协同边界很薄,稍不注意就会出现“本地有但 Redis 没更新”或者“Redis 已删但本地还留着”的状态撕裂,这类问题在线上几乎无法复现,只能靠读写路径的每一步加日志埋点。


















