一、缓存雪崩
问题本质
大量缓存数据在同一时间过期(失效)时,如果此时有大量的用户请求,都无法在 Redis 中处理,于是全部请求都直接访问数据库,从而导致数据库的压力骤增,严重的会造成数据库宕机,从而形成一系列连锁反应,造成整个系统崩溃
解决方案
-
分散过期时间
-
随机化过期时间:在基础过期时间上增加随机值(如
基础TTL + 随机1~10分钟
),避免集中失效。 -
代码示例:
java
复制
int baseTTL = 3600; // 基础过期时间1小时 int randomTTL = baseTTL + new Random().nextInt(600); // 增加0~10分钟随机值 redis.set(key, value, randomTTL);
-
-
缓存永不过期 + 异步更新
-
不依赖TTL,通过后台线程或消息队列异步更新缓存。
-
实现逻辑:
-
缓存不设过期时间,但记录数据的版本或更新时间戳。
-
通过定时任务或订阅数据库变更事件(如Binlog)触发缓存更新。
-
-
-
互斥锁限流
-
当缓存失效时,使用分布式锁(如Redis的
SETNX
)控制只有一个线程重建缓存,其他线程等待或返回降级结果。 -
代码示例:
java
复制
String lockKey = "lock:" + key; if (redis.set(lockKey, "1", "NX", "EX", 10)) { // 获取锁 try { // 查询数据库并重建缓存 } finally { redis.del(lockKey); // 释放锁 } } else { // 等待重试或返回默认值 }
-
- 集群分流
二、缓存击穿
问题本质
-
如果缓存中的某个热点数据过期了,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易就被高并发的请求冲垮
- 可以认为缓存击穿是缓存雪崩的一个子集
解决方案
-
热点数据永不过期
-
通过异步线程定期更新缓存(如每5分钟更新一次)。
-
优化点:结合双缓存策略(主缓存+备份缓存),确保更新期间仍有数据可用。
-
-
互斥锁重建
-
类似缓存雪崩的锁方案,但需注意锁粒度(应针对具体Key加锁而非全局锁)。
-
优化点:使用Redis的
RedLock
算法或本地锁(如Guava的Striped<Lock>
)减少锁竞争。
-
-
逻辑过期时间
-
缓存不依赖物理TTL,而是存储逻辑过期时间字段,由业务代码判断是否需异步更新。
-
数据结构示例:
json
复制
{ "value": "真实数据", "expire_time": 1710000000 // 逻辑过期时间戳 }
-
三、缓存穿透
问题本质
当发生缓存雪崩或击穿时,数据库中还是保存了应用要访问的数据,一旦缓存恢复相对应的数据,就可以减轻数据库的压力,而缓存穿透就不一样了。
当用户访问的数据,既不在缓存中,也不在数据库中,导致请求在访问缓存时,发现缓存缺失,再去访问数据库时,发现数据库中也没有要访问的数据,没办法构建缓存数据,来服务后续的请求。那么当有大量这样的请求到来时,数据库的压力骤增,这就是缓存穿透的问题。
解决方案
-
请求预校验
-
参数合法性校验:如ID必须为数字、长度固定、符合业务范围等。
-
示例代码:
java
复制
if (!isValidParam(param)) { // 校验参数格式 return "非法参数"; }
-
-
缓存空值(Null Object)
-
对查询结果为空的Key,缓存一个短TTL的空值(如
""
或NULL
)。 -
注意点:需定期清理空值,避免存储过多无效Key。
-
-
布隆过滤器(Bloom Filter)
-
前置过滤:在缓存层前加布隆过滤器,拦截不存在的数据请求。
-
实现步骤:
-
写入数据时,将Key同步到布隆过滤器。
-
查询时,先检查布隆过滤器:
-
若不存在,直接返回空。
-
若存在,继续查缓存或数据库。
-
-
-
优化点:
-
使用可扩展布隆过滤器(如RedisBloom模块)应对数据增长。
-
允许误判率(如1%),通过调整哈希函数数量和位数组大小平衡性能与精度。
-
-
四、方案对比与选型建议
场景 | 推荐方案 | 优点 | 缺点 |
---|---|---|---|
缓存雪崩 | 分散过期时间 + 异步更新 | 简单易实现,资源消耗低 | 需维护异步更新逻辑 |
缓存击穿(热点数据) | 逻辑过期时间 + 互斥锁 | 高并发下稳定性强 | 实现复杂度较高 |
缓存穿透(非法请求) | 布隆过滤器 + 空值缓存 | 高效拦截无效请求 | 布隆过滤器需维护数据同步 |
五、实践注意事项
-
监控与告警
-
监控缓存命中率、数据库QPS、Redis内存使用等指标,设置阈值告警。
-
-
降级策略
-
当缓存系统异常时,启用本地缓存(如Caffeine)或直接返回默认值,避免系统雪崩。
-
-
数据一致性
-
异步更新缓存时,需处理并发写冲突(如使用版本号或CAS机制)。
-
-
布隆过滤器维护
-
数据库删除数据时,需同步清理布隆过滤器(可通过延迟双删或订阅删除事件实现)。
-
通过上述方案组合,可显著提升缓存系统的稳定性和抗压能力。