缓存击穿需用set key val ex s nx原子操作防单key并发穿透,雪崩须在写入时加随机ttl(如base+rand(0,600))打散失效时间,二者机制互斥不可混用。

缓存击穿和雪崩必须分治,混用策略会放大问题——击穿靠原子写锁,雪崩靠随机TTL,两者底层矛盾点完全不同。
用 SET key val EX 300 NX 原子操作防击穿
击穿只发生在单个热点 key(如秒杀商品 ID)过期瞬间的并发查 DB 场景。关键不是“加锁”,而是让缓存回填这一步本身不可拆分。
-
GET→if nil→db.Get()→cache.Set()是典型错误:中间有竞态窗口,高并发下照样打穿 - 必须用 Redis 原生命令
SET key val EX 300 NX一气呵成,三者(设值、设过期、仅当不存在)由 Redis 保证原子性 -
NX锁的EX时间建议 ≥ 5s(略长于 DB 查询耗时),但别超 30s,否则故障时锁残留拖垮后续请求 - 别用
sync.Mutex或本地 map:多实例部署下完全无效;也别手写SETNX+GET+DEL三步锁,网络中断会导致死锁
用 rand.Int63n(600) 扰动 TTL 防雪崩
雪崩是大量热 key(如首页配置、商品列表)在同一秒过期,导致请求全量击穿到 DB。重点不是“不让过期”,而是打破时间强一致性。
- TTL 必须在
cache.Set(ctx, key, val, ttl)中一次性指定,别先SET再EXPIRE:网络失败会导致 key 永不过期 - 推荐公式:
baseTTL + time.Second*time.Duration(rand.Int63n(600))(±10 分钟扰动) - 别在
init()里调rand.Seed(time.Now().UnixNano()):goroutine 并发下容易撞种子;Go 1.20+ 直接用全局math/rand即可 - 如果用
redis.NewClusterClient,各节点系统时间偏差超过几秒就可能让实际过期不一致,务必开 NTP 校时
用 singleflight.Group 合并并发回源请求
当原子 SET 失败(比如 key 已存在但已过期)、或业务逻辑需更细粒度控制时,singleflight 是比自研锁更轻量、更安全的选择。
-
singleflight.Do(key, fn)保证同一 key 的多个并发请求只执行一次fn,其余等待共享结果 - 它不依赖 Redis,纯内存实现,无网络开销,适合单机部署或作为 Redis 原子操作的补充兜底
- 注意:它不能替代原子 SET 防击穿——因为
singleflight只管“执行一次”,不管“写缓存是否成功”;仍需在fn内完成SET并确保幂等 - 若服务横向扩展多实例,
singleflight仅作用于本机,此时必须回归 Redis 原子操作或分布式锁
最易被忽略的是:穿透、击穿、雪崩三者的触发条件和修复边界完全不同。空值缓存对击穿无效,随机 TTL 对穿透无效,singleflight 也无法解决雪崩——它们不是同一问题的不同解法,而是各自独立的防线。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











