Redis的缓存穿透、缓存击穿、缓存雪崩
在我们在查询数据时,在没有使用类似redis缓存的情况下,一般都是直接去数据库中查询,但是直接查询数据库的效率比使用redis缓存来查询数据的效率要低,所以我们需要增加缓存,但是使用redis缓存就存在一些问题,这里是对使用redis缓存的三类问题的总结。
1、缓存穿透
缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库。
解决方法:
-
缓存空对象
当我们客户端访问不存在的数据时,先请求redis,但是此时redis中没有数据,此时会访问到数据库,但是数据库中也没有数据,这个数据穿透了缓存,直击数据库,我们都知道数据库能够承载的并发不如redis这么高,如果大量的请求同时过来访问这种不存在的数据,这些请求就都会访问到数据库。为防止这个问题的发生,对于访问的这个数据在数据库中即使不存在,我们也把这个数据存入到redis中去,这样,下次用户过来访问这个不存在的数据,那么在redis中也能找到这个数据。
public void set(String key, Object value, Long time, TimeUnit unit){ stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value),time,unit); } ========================================================================================================= public <R,ID> R queryWithPassThrough(String keyPrefix,ID id,Class<R> type,Function<ID,R> dbFallback,Long time,TimeUnit unit){ //1.从redis查询缓存 String json = stringRedisTemplate.opsForValue().get(keyPrefix + id); //2.判断是否存在 if(StrUtil.isNotBlank(json)){ //3.存在,直接返回 return JSONUtil.toBean(json, type); } //判断命中的是否为空值 if(json!=null){ //这个是用来反馈信息的 return null; } //4.不存在,根据id查询数据库 //交给调用者 R r = dbFallback.apply(id); //5.不存在返回错误 if(r==null){ //空值写入redis stringRedisTemplate.opsForValue().set(keyPrefix+id,"",time,unit); return null; } //6.存在写入redis this.set(keyPrefix+id,r,time,unit); //7.返回 return r; }
-
布隆过滤器
布隆过滤器其实采用的是哈希思想来解决这个问题,通过一个庞大的二进制数组,走哈希思想去判断当前这个要查询的这个数据是否存在,如果布隆过滤器判断存在,则放行,这个请求会去访问redis,哪怕此时redis中的数据过期了,但是数据库中一定存在这个数据,在数据库中查询出来这个数据后,再将其放入到redis中,
假设布隆过滤器判断这个数据不存在,则直接返回
这种方式优点在于节约内存空间,存在误判,误判原因在于:布隆过滤器走的是哈希思想,只要哈希思想,就可能存在哈希冲
优缺点
-
优点
节省大量的内存空间、时间复杂度,节省内存空间是因为一个二进制向量。时间复杂度是根据映射函数查询,假设有K个映射函数,那么时间复杂度就是O(K)。
比较安全,因为存的不是元素本身,而是二进制向量
-
缺点
存在一定的误判。
不能删除布隆过滤器里的元素。
-
2、缓存雪崩
缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。
解决方法:在过期时间上增加⼀点随机值,另外如果搭建⼀个⾼可⽤的Redis集群也是防⽌缓 存雪崩的有效⼿段
3、缓存击穿
缓存击穿问题也叫热点Key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。
解决方法:
-
考虑这个热点key不设过期时间
-
使用互斥锁
因为锁能实现互斥性。假设线程过来,只能一个人一个人的来访问数据库,从而避免对于数据库访问压力过大,但这也会影响查询的性能,因为此时会让查询的性能从并行变成了串行。对于锁的实现可以通过redis中的setnx来实现。
当第一个人访问时,缓存并未命中,第一个人就需要拿到锁并从数据库中取出数据在redis中重新缓存数据,在第一个没有释放锁时,其他人的访问请求只能等待,直到第一个人释放锁后,在redis中可以查询到数据。