现象或背景:缓存穿透与雪崩的实战冲击
我们遇到了一个典型的高并发场景:某个热门商品页面的缓存突然失效,导致大量请求直接打到数据库。例如,商品ID为1001的商品,缓存失效后,10秒内有2000次请求涌入,数据库连接池瞬间被耗尽,服务响应时间从100ms飙升到1500ms,甚至出现连接超时。
具体数据:
- 缓存命中率:从95%骤降至60%;
- 数据库QPS:从500次/秒飙升到3000次/秒;
- 服务响应时间:从100ms增加到1500ms;
- 错误率:从0.5%上升到15%。
原理或根因分析:为什么缓存穿透与雪崩会发生?
1. 缓存穿透的定义与根因
缓存穿透是指某个热点数据在缓存中失效的瞬间,大量并发请求同时访问数据库,导致数据库压力骤增,甚至崩溃。例如,一个商品ID为1001的缓存失效,大量请求同时访问数据库查询该商品信息。
根因分析:
- 热点数据失效:缓存中的热点数据(如热门商品)突然失效,导致大量请求直接访问数据库;
- 缓存未命中:缓存未命中时,数据库压力骤增;
- 并发请求:大量并发请求同时访问数据库,导致数据库连接池耗尽。
2. 缓存雪崩的定义与根因
缓存雪崩是指在某一时间段内,大量缓存同时失效或未命中,导致数据库压力骤增。例如,某次促销活动后,大量缓存同时过期,导致数据库负载激增。
根因分析:
- 缓存过期时间设置不当:缓存过期时间设置为固定值,导致大量缓存同时失效;
- 缓存未命中率高:缓存未命中率高,导致大量请求直接访问数据库;
- 数据库压力过大:数据库无法处理大量并发请求,导致服务响应时间增加。
具体做法:解决方案与实施步骤
1. 使用互斥锁(Mutex)
在缓存失效时,使用互斥锁来确保只有一个线程去加载数据,其他线程等待。例如,使用Redis的SETNX命令或者Lua脚本来实现互斥锁。
# 使用Redis的SETNX命令
SETNX cache_key lock_value
2. 设置永不过期的缓存并采用逻辑过期时间
将缓存设置为永不过期,但在缓存中存储一个逻辑过期时间。当缓存访问时,检查逻辑过期时间,如果过期则加载数据并更新缓存。
# 设置永不过期的缓存
SET cache_key "value" NX PX 30d
3. 使用本地缓存(如Caffeine)
在应用层使用本地缓存来缓解缓存穿透问题。例如,使用Caffeine库来缓存热点数据。
// 使用Caffeine本地缓存
Cache<String, String> cache = Caffeine.newBuilder()
.maximumSize(1000)
.build();
4. 分布式锁(如Redisson)
使用分布式锁来确保在缓存失效时,只有一个线程去加载数据,其他线程等待。例如,使用Redisson的RLock来实现分布式锁。
// 使用Redisson分布式锁
RLock lock = redi
0赞
0收藏
0评论
3浏览