[学习笔记]缓存穿透、缓存雪崩,缓存击穿区别及解决

[学习笔记]缓存穿透、缓存雪崩,缓存击穿区别及解决

内容来源于:

  • https://www.cnblogs.com/dream-to-pku/p/9153999.html
  • https://www.cnblogs.com/dream-to-pku/p/9153999.html
  • https://blog.csdn.net/kongtiao5/article/details/82771694

1. 什么样的数据适合用缓存

image.png

2. 缓存处理流程

通常的流程是: 前台请求,后台先从缓存中取数据,取到直接返回结果,取不到时从数据库中取,数据库取到更新缓存,并返回结果,数据库也没取到,那直接返回空结果。


image.png

3. 缓存穿透

缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。这就导致数据库中并发的去执行了很多不必要的查询操作,从而导致巨大冲击和压力。流量大,可能DB就会挂掉了.要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。

3.1 描述

缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。

3.2 解决方案

  • 接口层增加校验: 如用户鉴权校验,id做基础校验,id<=0的直接拦截;
  • 缓存空对象: 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击.
  • 布隆过滤器: 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层数据库的查询压力。

缓存空对象问题拓展:

如果后面某个为空的记录在数据库中有数据了,该如何处理?

  • 可以提前设置缓存的时效性,等过期之后自然会到后端刷新新数据。
  • 如果时效性要求很高的话,那就采用数据库与缓存双写的模式来实现。
  • 如果时效性要求不高的话,可以设置一个消息队列,专门用来接收数据库记录变更的消息,然后由专门的缓存服务去取出新数据并更新到缓存中。

4. 缓存雪崩

4.1 描述

缓存雪崩是指在设置缓存时采用了相同的过期时间,导致缓存在某一时刻大量失效,导致所有的查询都落在数据库上,造成了缓存雪崩。和缓存击穿不同的是, 缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

4.2 解决方案

解决方案:

  1. 不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
  2. 设置热点数据永远不过期。如果缓存是分布式部署,将热点数据均匀分布在不同得缓存数据库中。
  3. 做二级缓存,或者双缓存策略。A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期。
  4. 可以通过缓存reload机制,预先去更新缓存,在即将发生大并发访问前手动触发加载缓存。
  5. 在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。

5. 缓存击穿

5.1 描述

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。
对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。

5.2 解决方案

  • 设置热点数据永远不过期。
  • 加互斥锁,互斥锁参考代码如下:https://www.cnblogs.com/dream-to-pku/p/9153999.html
static Lock reenLock = new ReentrantLock();
 
    public List getData04() throws InterruptedException {
        List 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;
    }

说明:

1)缓存中有数据,直接走上述代码13行后就返回结果了

2)缓存中没有数据,第1个进入的线程,获取锁并从数据库去取数据,没释放锁之前,其他并行进入的线程会等待100ms,再重新去缓存取数据。这样就防止都去数据库重复取数据,重复往缓存中更新数据情况出现。

参考:

  • https://www.cnblogs.com/dream-to-pku/p/9153999.html
  • https://blog.csdn.net/kongtiao5/article/details/82771694
  • https://blog.csdn.net/sun_qiangwei/article/details/80388318

你可能感兴趣的:([学习笔记]缓存穿透、缓存雪崩,缓存击穿区别及解决)