Redis缓存击穿、雪崩、穿透
1. 缓存击穿
1.1 触发原因
redis中,缓存击穿的含义是指,当某个热点数据的缓存突然失效,大量请求直接访问数据库的情况:
public Result<Data> findProductData(String dataId) {//在Redis中查询Object o = redisTemplate.opsForValue().getKey(dataId);if(o != null) {return new Result<>((Data)o);}//在数据库中查询Data data = productMapper.findById(dataId);if(data == null) {return new Result<>(null);}//把数据存入RedisredisTemplate.opsForValue().set(dataId, data, Duration.ofHours(2L);return new Result<>(data);}
上面代码中,如果key失效了,那么大量的并发请求就会直接访问数据库,就像是redis被击穿了,所以形象的称为缓存击穿。
1.2 解决方案:
- 设置永不过期:经过上面介绍,我们知道,缓存击穿是由于key失效引起的,那么我们将key设置为永不过期就可以解决这个问题,当然,redis的内存不是无限的,所有我们不能把所有key都设置为永不过期,具体的我们可以检测key的访问频率,把 "足够热门" 的key设置为永不过期即可。
- 加锁排队:发生缓存击穿后,会突然有大量请求并发访问数据库,那么我们可以在缓存击穿后通过加锁的方式,让请求串行到达数据库
public Result<Data> findProductData(String dataId) {//在Redis中查询Object o = redisTemplate.opsForValue().get(dataId);if(o != null) {return new Result<>((Data)o);}synchronized(this) {//双重检测,其它线程可能已经把key又存入rediso = redisTemplate.opsForValue().get(dataId);if(o != null) {return new Result<>((Data)o);}//在数据库中查询Data data = productMapper.findById(dataId);if(data == null) {return new Result<>(null);}//把数据存入RedisredisTemplate.opsForValue().set(dataId, data, Duration.ofHours(2L));return new Result<>(data);}}
2. 缓存雪崩
2.1 触发原因
当缓存中多个key集中失效时,大量请求直接访问数据库,造成数据库压力暴增,甚至宕机
2.2 解决方案
通过上面介绍我们知道,导致缓存雪崩的原因是多个key集中失效,那么我们就可以对症下药,随机key的失效时间,尽量避免集中失效的情况:
public Result<Data> findProductData(String dataId) {//在Redis中查询Object o = redisTemplate.opsForValue().get(dataId);if(o != null) {return new Result<>((Data)o);}synchronized(this) {//双重检测,其它线程可能已经把key又存入rediso = redisTemplate.opsForValue().get(dataId);if(o != null) {return new Result<>((Data)o);}//在数据库中查询Data data = productMapper.findById(dataId);if(data == null) {return new Result<>(null);}//设置随机失效时间Duration duration = Duration.ofHours(2L).plus(Duration.ofSeconds((int)(Math.random() * 100)));//把数据存入RedisredisTemplate.opsForValue().set(dataId, data, duration);return new Result<>(data);}}
3. 缓存穿透
3.1 触发原因
缓存穿透是指,请求访问了数据库中不存在的数据,数据不存在也就代表缓存中不可能有,就会直接访问数据库,这种情况可能是服务器受到了攻击。
3.2 解决方案
把空数据也缓存到redis:在之前的代码中,我们对于不存在的数据不会做缓存,那么如果收到大量访问不存在数据的请求就会导致数据库压力过大,于是我们可以去掉判断让不存在数据key也能缓存到redis:
public Result<Data> findProductData(String dataId) {//在Redis中查询Object o = redisTemplate.opsForValue().get(dataId);if(o != null) {return new Result<>((Data)o);}synchronized(this) {//双重检测,其它线程可能已经把key又存入rediso = redisTemplate.opsForValue().get(dataId);if(o != null) {return new Result<>((Data)o);}//在数据库中查询Data data = productMapper.findById(dataId);//设置随机失效时间Duration duration = Duration.ofHours(2L).plus(Duration.ofSeconds((int)(Math.random() * 100)));//把数据存入RedisredisTemplate.opsForValue().set(dataId, data, duration);return new Result<>(data);}}
这里也可以使用布隆过滤器,把不存在数据的key记录下来,每次请求时先判断布隆过滤器中有无该key,如果没有则继续在redis或数据库中查询,查询到空数据则把key记录到布隆过滤器中,表示这个key并无数据,可以类比黑名单的含义。