Redis 数据一致性:缓存穿透、击穿、雪崩的完整防护

CLARA轻量论坛系统
CLARA轻量论坛系统 星耀SVIP管理员 黑卡会员
发布于 2026-09-17 12:43 ·5 浏览 ·0 回复

三个缓存问题的根因完全不同:穿透是「查了根本不存在的数据」,击穿是「单个热点 key 过期瞬间被并发打穿」,雪崩是「大量 key 同时失效或 Redis 本身挂了」;防护的核心就三句话——拦住不存在的查询、别让热点 key 同时失效、任何时候都有不依赖 Redis 的兜底。把这三层做齐,再配合「先更新库、再删缓存 + 延迟双删」的一致性策略,Redis 缓存才算真正可用。

先分清根因,别用同一套药方

结论:穿透、击穿、雪崩是三种病,混着治只会浪费成本。

  • 穿透:请求的 key 在缓存和数据库里都不存在(典型是恶意构造随机 ID),每次请求都穿透到 DB。
  • 击穿:某个热点 key 恰好过期,成千上万并发同时回源查同一条数据。
  • 雪崩:大批 key 在同一秒过期,或者 Redis 宕机后所有流量瞬间砸向数据库。

穿透是「量的问题」,击穿是「点的问题」,雪崩是「面 + 可用性的问题」。下面逐个给方案和参数。

缓存穿透:布隆过滤器 + 短 TTL 空值缓存

结论:标准做法是「布隆过滤器挡第一层,空值缓存挡第二层」,单用任何一个都有漏洞。

布隆过滤器(Bloom Filter)是一个位数组加多个哈希函数,它只能说「一定不存在」或「可能存在」,不会漏判但会有误判。把全量合法 ID 预先装载进去,查询前先过一遍,判定不存在就直接返回。参数上,误判率 1% 大约需要每个元素 10 bit 空间、7 个哈希函数;容量按「预估元素数 × 1.2」预留,并定期重建(布隆不支持删除)。

空值缓存是兜底:DB 查不到时,往 Redis 写一个特殊空值占位,TTL 设短一点,60~300 秒即可。注意 value 必须能区分「空值」和「未命中」,否则业务代码会把空值当缓存未命中反复回源。

两层配合的逻辑是:布隆过滤器拦掉绝大多数随机 key,少量误判漏进来的由空值缓存接住,数据库基本不会被无效查询打穿。

缓存击穿:互斥锁或逻辑过期,二选一

结论:低并发场景用互斥锁,超高并发热点用逻辑过期——前者保证一致性,后者保证可用性。

互斥锁(单飞)方案:请求发现缓存未命中时,先抢锁:

SET lock:hotkey <requestId> NX PX 3000

拿到锁的线程去查 DB 并回填缓存,没拿到的睡 50ms 重试或直接返回旧值。两个细节必须做:一是锁一定要带过期时间(PX 3000 防死锁),二是释放锁要用 Lua 脚本比对 requestId,否则可能删掉别人的锁。

逻辑过期方案:value 里额外存一个 expireTime 字段,但 Redis 的 TTL 不设或设得很长。请求命中后判断逻辑时间是否过期:没过期直接返回;过期了就返回旧值,同时丢一个异步任务去重建缓存。代价是短时间内可能读到旧数据,换来的是所有请求都不阻塞。

热点 key 还可以直接「永不过期 + 后台定时刷新」,本质和逻辑过期是同一条路。

缓存雪崩:打散过期时间 + 多级兜底

结论:雪崩的防护重点是「别让 key 同时死」和「Redis 死了系统也别死」。

第一,过期时间加随机抖动。不要写死 1800 秒,改成 `1800 + random(0, 300)` 秒,让失效点均匀散开。第二,缓存预热:上线或重启后分批把热点数据加载回去,别用一次全量刷。

第三,上多级缓存:本地 Caffeine + Redis,本地 TTL 设得比 Redis 更短(比如 60 秒),Redis 抖动时本地还能顶一阵。第四,熔断降级:接 Sentinel 或 Hystrix,DB 压力超阈值时快速失败,返回兜底默认值而不是把连接池拖死。第五,Redis 本身要有主从 + 哨兵或 Cluster,AOF 用 everysec,别把可用性全押在单节点上。

一致性:先更新库再删缓存,加延迟双删

结论:缓存和数据库没有强一致,只能做最终一致;工程上优先「先更新数据库,再删除缓存」(Cache-Aside),并配合延迟双删兜底。

为什么不是先删缓存再更新库?因为删完缓存到库更新完成之间,如果有读请求进来,会把旧值重新写回缓存,脏数据可能长期存在。先更新库再删缓存,脏窗口只有「更新完成到删除完成」这一小段。

延迟双删:更新完数据库并删除缓存后,延迟 500ms~1s 再删一次,用来清掉并发读回填的旧值。如果主从分离,这个延迟必须大于主从复制延迟,否则从库读出的旧值又会被写回。

想要更强的一致性,就用 Canal 或 Debezium 订阅 binlog,异步触发缓存删除;删除失败丢进消息队列重试。最后一定要给所有缓存设 TTL——这是最重要的一致性保险,即使删缓存失败,数据也会自动过期自愈。

收尾

穿透靠布隆过滤器加空值缓存,击穿靠互斥锁或逻辑过期,雪崩靠过期抖动加多级缓存和熔断降级,一致性靠先更库再删缓存、延迟双删加 TTL 兜底。这五件事没有哪一件能单独解决问题,按顺序把每一层补齐,缓存才是收益而不是风险。

本文转载自 Clara轻量论坛系统,原文地址:https://www.leleweb.cn/thread-429.html
转载请注明出处,版权归原作者所有。

全部回复 0

还没有回复,来抢沙发~