击穿
什么是缓存击穿
就是例如某个爆款商品突然在redis中失效了,此时并发请求集中访问数据库,导致数据库压力暴增导致宕机的情况。那么如何解决这种情况?
如何解决
正常情况下我们使用redis查询数据的逻辑如下:
public TProduct selectById(Long id) {
String key = "product:" + id;
//从redis中查询数据
Object product = redisTemplate.opsForValue().get(key);
//如果查到则直接返回
if (product != null ){
return (TProduct) product;
}else{
//如果没有查到,则去数据库查询
product = productMapper.selectById(id);
if (product != null ){
//查完放入redis并设置过期时间2小时
redisTemplate.opsForValue().set(key,product, Duration.ofHours(2));
return (TProduct) product;
}else{
throw new ServiceException("产品不存在");
}
}
}
那么在缓存击穿的情况下,也就是如上代码中,该ID的商品信息从redis中失效了,此时大量的并发请求同时访问到了数据库,导致数据库压力剧增,此时该如何解决?
- 永不过期(不推荐)
- 加锁排队,synchronized和分布式锁都可以(推荐)
public TProduct selectById(Long id) {
String key = "product:" + id;
//从redis中查询数据
Object product = redisTemplate.opsForValue().get(key);
//如果查到则直接返回
if (product != null ){
return (TProduct) product;
}else{
synchronized (this){
//在这里再次查询一次redis,目的是为了并发请求时假如我是第二个线程此时卡在这里等待第一个线程释放锁,当第一个
//线程释放锁了之后,说明第一个线程已经将数据放入redis了,此时我们直接查询redis即可
product = redisTemplate.opsForValue().get(key);
if (product != null){
return (TProduct) product;
}
//如果没有查到,则去数据库查询
product = productMapper.selectById(id);
if (product != null ){
//查完放入redis并设置过期时间2小时
redisTemplate.opsForValue().set(key,product, Duration.ofHours(2));
return (TProduct) product;
}else{
throw new ServiceException("产品不存在");
}
}
}
}
那么使用这个锁的时候不会使性能降低吗?通过代码我们可以发现,其实只有在第一次失效的时候会排队等待,当第一个线程将数据放入redis后剩下的请求基本不会等待太久。
雪崩
什么是缓存雪崩
缓存雪崩的两种情况
- 缓存集中失效
- 缓存服务器宕机
如何解决
- 缓存集中失效:在上面击穿的例子中给每个key设置随机的失效时间即可
//查完放入redis并设置过期时间2小时
redisTemplate.opsForValue().set(key,product, Duration.ofHours((long) (Math.random() * 100)));
- 缓存服务器宕机:设置redis集群高可用
穿透
什么是缓存穿透
用户伪造大量的不存在的请求来攻击系统造成系统瘫痪
如何解决
- 参数校验(不推荐)
参数校验虽然可以部分解决非法参数的问题,但是本身工作量大缺点明显 - 布隆过滤器
布隆过滤器是一种数据结构,比以往的set list 等更加高效并且占用空间也少,我们可以将一些不存在的数据的ID放在布隆过滤器中或者是将数据库已有的数据ID放在布隆过滤器中,类似白名单 黑名单的功能 - 缓存空对象
不管能不能查到这个数据,都将其缓存到redis中并设置过期时间