本文目录导读:
这是一个非常经典的分布式系统高并发问题。缓存雪崩 是指:大量的缓存数据在同一时间过期失效,或者缓存节点(如Redis集群)整体宕机,导致所有请求瞬间直接打到数据库上,导致数据库压力剧增甚至崩溃。
针对不同的原因,预防方案也不同,以下是核心且实用的防雪崩策略,分为预防和兜底两个层面。
针对“缓存大面积同时过期”的情况
这是最常见的雪崩原因,核心目标是避免大量Key在同一时刻失效。
过期时间打散(随机化)
这是最基础、最有效的手段,不要给所有Key设置相同的过期时间,而是在基础过期时间上加上一个随机值(1-5 分钟)。
- 做法:
TTL = 基础时间 + Random(0, 300秒) - 效果: 避免“排队”失效,让失效时间均匀分布。
缓存永不过期(逻辑过期)
物理上不给缓存设置过期时间,但存一个逻辑过期时间字段,后台另起一个线程或定时任务,在发现逻辑过期时,异步更新缓存。
- 做法:
- 存数据时,同时存入
expire_time。 - 读取时,判断是否逻辑过期。
- 若过期,不直接返回null,而是先返回旧数据,同时后台异步更新缓存。
- 存数据时,同时存入
- 效果: 用户永远拿不到空数据,彻底防止失效导致的雪崩。缺点: 会有一小段时间的数据不一致,且增加维护复杂度。
互斥锁(Mutex Key)
当缓存失效时,只允许一个线程去数据库查询并重建缓存,其他线程等待(自旋重试)或快速失败。
- 做法: 在查询DB前,尝试获取分布式锁(如Redis Setnx),拿到锁的线程去查DB并写缓存;没拿到锁的线程休眠后重试。
- 优点: 保证只有一个请求打到DB,保护数据库。
- 缺点: 会导致系统吞吐量下降,增加响应延迟,且有死锁风险(需设置锁过期时间)。
针对“缓存节点宕机(Redis集群挂了)”的情况
这是更严重的雪崩,核心目标是保证缓存服务的高可用。
搭建高可用集群
这是最根本的解决方案,避免单点故障。
- 主从复制 + 哨兵模式: 主节点挂了,哨兵自动升级从节点为主。
- Redis Cluster: 数据分片存储,部分节点挂了不影响整体服务。
缓存预热 + 持久化快速恢复
不要等用户请求来重建缓存,要提前准备。
- 做法: 系统上线前,预先将热点数据加载进缓存,如果Redis宕机重启,利用RDB/AOF持久化文件快速恢复数据,避免冷启动导致所有请求直接落数据库。
多级缓存(本地缓存 + 分布式缓存)
即使Redis集群挂了,应用层还有一层“兜底”的本地缓存(如Caffeine、Guava Cache)。
- 架构:
L1(本地缓存) -> L2(Redis) -> DB - 效果: 当Redis不可用时,本地缓存依然能扛住大部分读请求,给Redis恢复争取时间。
核心兜底策略(限流降级)
无论预防做得再好,极端情况下仍有可能突破防线。一定要有最后的“熔断”措施。
数据库访问限流(服务端保护)
当确实需要访问数据库时,对数据库的流量进行控制。
- 做法: 使用Sentinel、Hystrix等组件,设置“数据库连接池”的最大线程数,当请求超过阈值时,直接返回“服务繁忙”或降级数据(如默认值、旧缓存等),而不是让请求继续压垮数据库。
服务降级(返回友好提示或默认值)
当检测到雪崩已经发生,主动放弃非核心功能,保证核心链路通畅。
- 做法: 如果获取不到数据,不再去重试或查询DB,直接返回预设的默认值(如首页的默认推荐列表、商品详情页缓存过的快照等),或返回一个“稍后重试”的提示。
最佳实践组合拳
没有单一方案能应对所有场景,建议结合使用:
- 源头预防: 过期时间加随机值(最简单有效)。
- 架构高可用: 搭建Redis Cluster + 本地缓存(如Caffeine)做双保险。
- 逻辑兜底: 对热点数据使用逻辑过期(异步更新)。
- 最后防线: 数据库访问必须加限流/熔断组件(如Sentinel),防止数据库被打死。
面试话术(简洁版/建议)
“针对缓存雪崩,我通常会从三个维度处理: 一是预防失效集中,给缓存过期时间加随机值,或者对极端热点数据使用‘逻辑过期+异步更新’模式。 二是保证缓存高可用,比如搭建Redis Cluster,同时引入本地缓存做多级缓存。 三是兜底熔断,在数据库访问层增加限流(如Sentinel),当缓存不可用或DB压力过大时,自动降级返回默认数据,确保系统不彻底崩溃。”
标签: 限流降级