缓存雪崩主因是大量 key 过期时间设置过于集中,应使用随机 ttl 错峰失效;推荐写法为 redis.set(ctx, key, val, basettl+time.second*time.duration(rand.int63n(600))),basettl≥5分钟,禁用先 set 再 expire 和绝对过期时间。

缓存雪崩不是 Redis 崩了,是业务层把大量 key 的过期时间设得太齐——比如全用 30 * time.Minute,服务重启或批量预热后,它们在同一秒集体失效,高并发请求瞬间压垮数据库。
随机 TTL 必须在 SET 时一次性指定
Redis 不会帮你加抖动,所有扰动逻辑必须由 Go 代码控制。关键不是“加不加”,而是“怎么加才安全”。
-
redis.Set(ctx, key, val, baseTTL + time.Second*time.Duration(rand.Int63n(600)))是唯一推荐写法;baseTTL建议 ≥5 * time.Minute,否则抖动后部分 key 过期太快,起不到错峰作用 - 绝对不要先
SET再单独调EXPIRE:网络失败会导致 key 永不过期,或只设成功一半 - 别用
time.Now().Add(...)算绝对过期时间——集群节点时钟不同步(哪怕差 2 秒)就会让实际过期不一致;改用相对 TTL - Go 1.20+ 直接用
rand.Int63n(600)即可,不用手动rand.Seed();若用旧版本,每次生成新rand.New(rand.NewSource(time.Now().UnixNano()))
预热不能靠启动时批量 GET/SET
服务刚起来就扫热 key,等于主动制造 DB 尖峰。预热本质是“错峰补位”,不是“集中加载”。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 懒加载 + 过期前异步刷新更稳:读取时发现剩余 TTL
- 异步刷新必须走 worker pool 或带 buffer 的 channel,不能裸写
go refresh()——没上下文、panic 丢失、无法限流 - 如果真要启动预热,必须节流:按 key 分片 + 间隔 sleep + 失败重试,避免 DB 连接池被打满
- Redis Cluster 场景下,预热前确认各节点 NTP 已校时,否则预热的 key 在不同节点上实际过期时间偏差可能超预期
singleflight 防击穿 ≠ 防雪崩,别混用
雪崩靠时间打散,击穿靠请求合并,两者策略冲突。混用 singleflight 做雪崩防护,只会增加延迟、放大故障面。
-
singleflight.Group必须声明为包级变量或注入 service 结构体,不能在 handler 里 new——否则每个请求都是新实例,Do完全无效 - 所有副作用操作(DB 查询、
cache.Set、日志)必须全部塞进Do回调里,漏掉任一环,竞态窗口仍在 - 别用
sync.Mutex或本地 map 实现“伪 singleflight”——多实例部署下完全无效 - 锁粒度必须按 key 切分,不能用全局锁;但也不建议手写
map[string]*sync.Mutex,容易内存泄漏,优先用singleflight
最易被忽略的一点:雪崩防护效果高度依赖基础 TTL 设置。如果基础值太短(比如 10 * time.Second),哪怕加了 ±5 分钟抖动,仍有大量 key 在几秒内过期——这已经不是错峰,是持续抖动。真正有效的雪崩防护,是让“过期”变成一个宽泛的时间段,而不是一个精确的时间点。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










