缓冲--01--常见缓冲问题及解决方案

缓存穿透、缓存击穿、缓存失效

1. 使用缓存存储数据的步骤

1、先查询缓存,如果没有数据,再去查询数据库
2、查询完数据库之后,如果数据不为空,再将结果写入缓存

2. 缓存穿透

1. 什么叫缓存穿透?
  • 一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB)。如果key对应的value是一定不存在的,并且对该key并发请求量很大,就会对后端系统造成很大的压力。
    也就是说,对不存在的key进行高并发访问,导致数据库压力瞬间增大,这就叫做缓存穿透。
  • 如何解决?
  1. 对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存。
  2. 对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤。(布隆表达式)

3. 缓存雪崩

1. 什么叫缓存雪崩?

当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力。

  • 如何解决?
    1. 在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
    2. 不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
    3. 做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期(此点为补充)

4. 缓存击穿

  • 什么叫缓存击穿?
    • 对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。

    • 缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

  • 如何解决?
    • 使用redis的setnx互斥锁先进行判断,这样其他线程就处于等待状态,保证不会有大并发操作去操作数据库。
      if(redis.sexnx()==1){
          //先查询缓存
      	//查询数据库
      	//加入缓存
      }
      

5. 数据库和缓存双写一致性问题

  • 一般来说,在读取缓存方面,我们都是先读取缓存,再读取数据库的。
    但是,在更新缓存方面,我们是需要先更新缓存,再更新数据库?还是先更新数据库,再更新缓存?还是说有其他的方案?
一、先更新数据库再更新缓存(不建议使用)
  • 操作步骤(线程A和线程B都进行更新操作):
    1.线程A更新了数据库
    2.线程B更新了数据库
    3.线程B更新了缓存
    4.线程A更新了缓存
  • 问题1:脏读
  • 问题2:浪费性能
二、先更新数据库再删除缓存
  • 操作步骤(线程A更新、线程B读)
    请求A进行写操作,删除缓存
    请求B查询发现缓存不存在
    请求B去数据库查询得到旧值
    请求B将旧值写入缓存
    请求A将新值写入数据库

  • 解决方案1:延时双删策略,伪代码如下:

    public void write(String key, Object data){
        redis.delKey(key);
        db.updateData(data);
        Thread.sleep(1000);
        redis.deleKey(key);
    }
    
  • 解决方案2:使用消息队列

三、先删除缓存再更新数据库
  • 操作步骤
    1.用户A删除缓存失败
    2.用户A成功更新了数据
      或者
    1.用户A删除了缓存;
    2.用户B读取缓存,缓存不存在;
    3.用户B从数据库拿到旧数据;
    4.用户B更新了缓存;
    5.用户A更新了数据。
  • 问题:脏数据
  • 解决方案1:设置缓存有效时间(最简单)
  • 解决方案2:使用消息队列
©️2020 CSDN 皮肤主题: 像素格子 设计师:CSDN官方博客 返回首页