缓存穿透和缓存雪崩
缓存穿透 (查不到)
数据库中没有, 缓存中自然没有, 所以就会频繁的去数据库中查.
布隆过滤器
布隆过滤器式一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力.
pravate static BloomFilter<Integer> bloomFilter = BloomFilter.create(FUnnels.integerFunnel(),size,fpp);
//本质是一个集合 内置是bitmaps 利用的是 位图 0 1 m
利用若干 个HASH 函数 ,先把所有数据添加进去,新的来了 查看 只要有一个为0 那么就是一定不存在,若都为一 ,可能存在,这个时候先去redis 找,找不到再去数据库。
缺点:维护比较麻烦,而且不能删除 所以要定期维护
缓存空对象
当存储层不命中后,将返回的空对象存储起来,同时设置一个过期时间,之后在访问这个数据就会从缓存中获取
缓存击穿(量太大,缓存过期)
一个数据刚刚好失效,或者缓存中没有这个数据 这个时候并发访问.
也就是缓存击穿是量太大了,一个key非常热点,大并发集中对这一个点进行访问,这个key失效的瞬间,持续的大并发就穿破缓存,直接请求数据库
设置热点数据永不过期
但是永不过期 会有别的问题
加互斥锁
分布式锁: 使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可.
这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大.
- 为什么需要分布式锁
如果原本我们的系统分布在一台机器上的时候,JVM提供的锁就能解决并发问题。但是如果我们是使用多台机器,要同时去Redis里面去拿同一个Key 这个时候,就会发生并发问题,因为这个时候JVM的锁事无法解决这个问题。 - 基于Resid的分布式锁
-
最简单的方法是使用setnx 命令,Key 是锁的唯一标识(加锁)
- setnx key value :是去redis尝试set 如果里面已经有key 那么就设置失败返回0
ps:这里其实就是利用的redis的原子性 实现了CAS的效果。
-
有加锁就得有解锁。当得到锁的线程执行完任务,需要释放锁,以便其他线程可以进入。释放锁的最简单方式是执行
del
指令(解锁)- del key:删去Key 达到解锁的效果
- 锁超时
因为如果宕机 不给锁设置超时,那么就会死锁 ,所以在 setnx的时候需要设置超时时间。expire key times - 问题!
- 第一点: 在加锁和锁超时的时候 不是原子操作
- 解决方案:可以利用lua ,set 可以添加可选参数
- del 误删:如果某个业务逻辑的时间超过了超时时间,这个时候如果key 已经被删除了就会有第二个逻辑进来操作 从而没有达到锁的效果。
- 解决方案:使用守护线程 一直去给我们的key 加上超时时间,这样会让锁不被释放。
- 守护线程:开辟一个线程 如果主线程挂了或者结束 那么守护线程也会结束。
- 解决方案:使用守护线程 一直去给我们的key 加上超时时间,这样会让锁不被释放。
缓存雪崩
在某一个时间段,缓存集中过期失效,或者Redis宕机.
redis高可用
既然redis有可能挂,就多增设几台redis
即高可用高效集群
限流降级
在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量.
数据预热
在正式部署前,先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中. 在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀.