Redis中的缓存击穿、穿透、雪崩
- 缓存击穿:指某个热点数据在缓存中失效,导致大量请求直接访问数据库。此时,由于瞬间的高并发,可能导致数据库崩溃
- 缓存穿透:指查询一个不在的数据,缓存中没有相应的记录,每次请求都会去数据库查询,造成数据库负担加重
- 缓存雪崩:指多个缓存数据在同一时间过期,导致大量请求同时访问数据库,从而造成数据库瞬间负载激增
解决方案
缓存击穿
- 使用互斥锁,确保同一时间只有一个请求可以去数据库中查询并更新缓存
- 热点数据永不过期
缓存穿透
- 使用布隆过滤器,过滤掉不存在的请求,避免直接访问数据库
- 对查询结果进行缓存,即使是不存在的数据,也可以缓存一个标识,以减少对数据库的请求
缓存雪崩
- 采用随机过期时间策略,避免多个数据同时过期
- 使用双缓存策略,将数据同时存储在两层缓存中,减少数据库直接请求
缓存击穿
缓存击穿指的是某一热点数据缓存失效,使得大量请求直接打到了数据库,增加数据库负载
场景:大家在网购平台抢购茅台,但在某一时刻茅台的缓存失效了,所有的请求全部打到了数据库,这就是缓存击穿
互斥锁实现
public class CacheService {
private Jedis jedis = new Jedis("localhost");
private Lock lock = new ReentrantLock();
public String getData(String key) {
// 尝试从缓存获取数据
String value = jedis.get(key);
// 如果缓存不存在
if (value == null) {
// 加锁以防止并发请求
lock.lock();
try {
// 再次检查缓存,避免重复查询
value = jedis.get(key);
if (value == null) {
// 查询数据库
value = queryDatabase(key);
// 将结果放入缓存
jedis.set(key, value);
}
} finally {
// 释放锁
lock.unlock();
}
}
return value;
}
}
说明
- 缓存查询:先尝试从Redis中获取数据
- 加锁:如果缓存中没有数据,使用ReentrantLock加锁,确保只有一个线程可以查询数据库
- 二级检查:在加锁后再次检查缓存,避免重复查询
- 数据库查询:如果缓存仍没有数据,查询数据库并缓存起来
- 释放锁:确保锁在查询结束后被释放,防止死锁
缓存穿透
缓存穿透是指一个不存在的数据,由于缓存中不存在,导致每次请求都直接访问数据库,增加数据库负载
攻击者可以通过构造不存在的key发起大量请求,对数据库造成很大的压力,可能会造成系统宕机
解决方案
- 防止非法请求:检查非法请求,封禁其IP以及账号
- 缓存空值:将数据库中不存在的结果也缓存起来,并设置一个较短的过期时间,避免频繁查询数据库
- 使用布隆过滤器:使用布隆过滤器来快速判断一个请求的数据是否存在,如果布隆过滤器判断数据不存在,则直接返回,避免查询数据库
缓存雪崩
缓存雪崩是指在某个时间点,大量缓存同时失效或被清空,导致大量请求直接打到了数据库,造成负载,甚至引发系统崩溃
解决方案
- 过期时间随机化:设置缓存过期时间,加上一个随机值,避免同一时间大量缓存失效
- 使用多级缓存:引入多级缓存机制,如本地缓存和分布式缓存相结合,减少单点故障风险
- 缓存预热:系统启动时提前加载缓存数据,避免大量请求落到冷启动状态下的数据库
- 加锁互斥:使得没有缓存或缓存失效的情况下,同一时间只有一个请求访问数据库