缓存击穿、穿透、雪崩本质是业务层对原子性、ttl、空值序列化等细节失控,须分治:击穿用set+nx+ex原子操作防单key并发打穿;穿透用序列化“假nil”空值兜底;雪崩靠随机ttl打散过期时间。

缓存击穿、穿透、雪崩不是 Redis 故障,而是业务层对 SET 原子性、TTL 扰动、空值序列化、锁过期时间等细节没控住——三者必须分治,混用策略会互相干扰甚至放大问题。
用 SET + NX + EX 原子操作防击穿
击穿只针对单个热点 key(如秒杀商品 ID),过期瞬间并发查 DB。关键不是“加锁”,而是让写缓存这一步本身具备排他性。
-
SET key val EX 300 NX必须一气呵成:Redis 保证设置值+过期+仅当不存在时才成功,三者不可拆分 - 别写
GET→if nil { db.Get(); cache.Set() }:中间有竞态窗口,高并发下照样打穿 - 锁的
EX时间得比 DB 查询耗时长(建议 ≥ 5s),但别超 30s,否则故障时锁残留拖垮后续请求 - 不用
sync.Mutex或本地 map:多实例部署下完全无效;也别手写SETNX+GET+DEL三步锁,网络中断会导致死锁
空值缓存必须序列化“假 nil”,不能传 Go 的 nil
穿透本质是恶意或误构造的 key(如 id=-1、user:abc)反复查 DB。解决方案不是拦截请求,而是让缓存对“不存在”返回明确响应。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- DB 查不到时,往 Redis 写入可反序列化的占位符,比如
"null"或{"exists":false},**绝不能传 Go 的nil** - 读取后要显式判断:反序列化完检查是否为
"null"字符串,再决定是否走 DB,否则空字符串会被当成真实数据返回 - 空值
TTL设短(如2 * time.Minute),避免长期污染;也别设永久,误判后恢复成本高 -
redis.Nil是客户端错误类型,不是业务空值——它表示命令执行失败(如连接断了),不是“查不到”
随机 TTL 打散过期时间,防雪崩
雪崩是大量热 key(如首页配置、商品列表)在同一秒过期,导致请求全量击穿到 DB。重点不是“不让过期”,而是打破时间强一致性。
- 设置
TTL时叠加随机秒数:baseTTL + time.Second*time.Duration(rand.Int63n(600))(±10 分钟) - 必须在
cache.Set(ctx, key, val, ttl)里一次性指定,别先SET再EXPIRE:网络失败会导致 key 永不过期 - 别在
init()里调rand.Seed(time.Now().UnixNano()):goroutine 并发下容易撞种子;Go 1.20+ 用全局math/rand即可 - 如果用
redis.NewClusterClient,各节点系统时间偏差超过几秒就可能让实际过期不一致,务必开 NTP 校时
最易被忽略的点:三个问题的触发条件和修复边界完全不同——穿透靠空值兜底,击穿靠原子写锁,雪崩靠 TTL 扰动。把布隆过滤器用在用户 ID 这类动态 key 上,重建成本高、误判率升,反而增加复杂度;而给所有 key 都加互斥锁,又会拖慢非热点路径。策略得按 key 特征分层落地,不是套模板。










