目录
1 缓存穿透
1.1 本质及危害
-
缓存穿透本质:客户端访问的数据未进行缓存,访问请求找不到缓存从而向数据库请求,就发生了缓存穿透。
-
危害
缓存和数据库中都没有客户端请求的数据,请求直接打到数据库上并且还是查不到的数据(eg:null)没办法写进缓存,造成数据库短时间线程数被打满,导致其他服务阻塞,最终导致线上服务不可用。
1.2 造成缓存穿透的原因
- 缓存容量有限,不可能将所有数据都缓存,查询到未被缓存的数据就可能发送缓存穿透。
- 互联网业务数据访问模型一般遵循二八原则:20%热点数据,80%不常访问数据,对于热点数据我们利用缓存机制,不常访问数据即使发送缓存穿透,数据库也顶得住。
1.3 解决方案
-
接口参数校验
在请求的参数校验层加上合法性校验(eg:查询订单ID为20位,校验长度是否刚好20位,不符合的就直接过滤掉)
-
设置空值缓存
当访问请求在缓存和数据库中都找不到时,将该请求(key)的值缓存为null,设置较短的过期时间。
-
缺陷
这种方式存在一定的坏处:若是有大量不存在值的恶意请求访问,会缓存大量的null,造成资源浪费,因此我们要做好监控,防止缓存空间被过多的null占用。
-
-
布隆过滤器
布隆过滤器中存储所有可能被访问的key,在有访问请求时,会先过滤掉不合法的请求(key不存在),存在key的请求再进一步去查询缓存、数据库。
-
底层实现
布隆过滤器底层是一个bitSet 和一组Hash函数组成,通过二进制来存储数据,空间效率高。
-
缺陷
-
存在误判
由于是用Hash函数算法实现,可能存在Hash碰撞,可能存在误判(不同值的数据可能获得相同的hash值,进而导致非法的key被误判为合法或请求的结果错误)。
-
删除困难
在hash碰撞的情况下删除数据,可能影响其他的数据(eg:gg和bb发送hash碰撞共享一个hash值,当要删除gg时,可能把gg和bb一起删除了)。
-
2 缓存击穿
缓存击穿:高并发访问缓存中没有但数据库有的数据,使得数据库压力瞬间增大,造成系统卡住。
解决方案
1.互斥锁
在并发请求中,让第一个请求的线程拿到锁并执行数据库查询操作,其他线程阻塞等待,直到第一个线程查询完毕并把数据缓存后,其他线程在缓存中获取数据。
//互斥锁处理
static Lock reenLock = new ReentrantLock();
public List<String> getData04() throws InterruptedException {
ArrayList<String> result = new ArrayList<>();
//从缓存读取数据
result = getDataFromCache();
if(result.isEmpty()){
if(reenLock.tryLock()){ //拿到锁的情况
try{
System.out.println("拿到锁,从DB获取数据后写入缓存");
//从数据库查询数据
result = getDataFromDB();
//将查到的数据写入缓存
setDataToCache(result);
}finally {
//释放锁
reenLock.unlock();
}
}else{ //未拿到锁
result= getDataFromCache();
if(result.isEmpty()){
System.out.println("没获取到锁,等待.....");
Thread.sleep(100);
return getData04(); //重新访问
}
}
}
return result;
}
2.热点数据设置不过期
在redis中维护一个热点数据表,批量设置为不过期数据(eg:top1000),并定时更新热点数据。
这种方式在流量大的场景需要考虑业务鞥你发接收数据不一致的时间以及异常情况处理,避免缓存刷新不上,一直都是脏数据。
3. 缓存雪崩
缓存雪崩:大量key设置相同的过期时间,导致缓存在同一时间失效,并且刚好有大量请求,导致数据库瞬间请求量大增,引起雪崩。(或者是redis宕机了,所有请求直接向数据库访问,造成雪崩)
解决方案
1.过期时间打散
缓存的过期时间加上个随机值时间,使得每个key过期时间分散开来,不在同一时间过期。
2.热点数据不过期
热点数据设置不过期,异步更新缓存数据。(可能导致数据不一致)
3.互斥锁
和缓存击穿一样按key加锁,对于同一个key只允许一个线程去访问,其他线程阻塞等待获取锁的线程的结果。