如果你还对面试常问的缓存击穿、缓存穿透和缓存雪崩不了解,那么你一定得看看

大家好,我是指北君。

对于使用过Redis的同学,一定听过缓存击穿、缓存穿透或者缓存雪崩吧?这是缓存系统最常见的几个问题。 但是我相信很多同学对这三个之间的概念都是模模糊糊的,今天这篇文章就是为了说明这三者之间的区别,以及如果解决这些问题。希望你在面试相关问题时可以准确的回答。

缓存穿透

一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB)。如果key对应的value是一定不存在的,并且对该key并发请求量很大,就会对后端系统造成很大的压力。

也就是说,对不存在的key进行高并发访问,导致数据库压力瞬间增大,这就叫做【缓存穿透】。

解决方案: 对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存。

缓存雪崩

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

突然间大量的key失效了或redis重启,大量访问数据库而导致的系统压力剧增问题就是缓存雪崩啦。

解决方案:

  1. key的失效期分散开,不同的key设置不同的有效期;
  2. 设置二级缓存;
  3. 高可用方案,比如redis集群,保证不会因为缓存系统崩溃而导致缓存雪崩;

缓存击穿

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

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

解决方案:

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

缓存双写一致性

这个问题是业务开发中常见的问题,那就是在写redis和数据库时如何保证数据的一致性呢? 如果你还不了解什么情况下会出现双写一致性问题,那么下面我来举几个例子,大家看看是不是很熟悉?你们平常是不是就是这么来使用redis的呢?

先更新数据库再更新缓存(不建议使用);

操作步骤(线程A和线程B都对同一数据进行更新操作):

  1. 线程A更新了数据库
  2. 线程B更新了数据库
  3. 线程B更新了缓存
  4. 线程A更新了缓存

显而易见,这面这种操作的问题在于:脏读、浪费性能

先更新数据库再删除缓存

操作步骤:

  1. 请求A进行写操作,删除缓存,此时A的写操作还没有执行完
  2. 请求B查询发现缓存不存在
  3. 请求B去数据库查询得到旧值
  4. 请求B将旧值写入缓存
  5. 请求A将新值写入数据库

此方案,可以看到在步骤4会将旧值最后写入缓存,最终造成脏读;

先删除缓存再更新数据库

操作步骤:

  1. 用户A删除缓存失败
  2. 用户A成功更新了数据

或者:

  1. 用户A删除了缓存;
  2. 用户B读取缓存,缓存不存在;
  3. 用户B从数据库拿到旧数据;
  4. 用户B更新了缓存;
  5. 用户A更新了数据;

按照上面的步骤,此方案也是会出现脏读问题,导致数据双写不一致而引发业务系统异常。


这里给出几种解决方案:

  • 解决方案1:设置缓存有效时间(最简单),在接受最终一致性的场景下,配置合理的失效时间。
  • 解决方案2:使用消息队列,例如rocketMq等消息队列可以保证数据操作顺序一致性,确保缓存系统的数据正常。

这就是Redis缓存系统经常问的 缓存击穿、缓存雪崩、缓存击穿 之间的区别,虽然这几个概念听着非常的相似,实质上在定义上还是有一些细微的区别。同时也介绍了在业务系统使用中常见的 数据双写缓存一致性 问题的场景和推荐的解决方案,希望能帮助到大家。

感谢各位小伙伴的:点赞、收藏和评论,我们下期更精彩!

Java Geek Tech wechat
欢迎订阅 Java 技术指北,这里分享关于 Java 的一切。