大家好,我是指北君。
对于使用过Redis的同学,一定听过缓存击穿、缓存穿透或者缓存雪崩吧?这是缓存系统最常见的几个问题。 但是我相信很多同学对这三个之间的概念都是模模糊糊的,今天这篇文章就是为了说明这三者之间的区别,以及如果解决这些问题。希望你在面试相关问题时可以准确的回答。
缓存穿透 {#缓存穿透}
一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB)。如果key对应的value是一定不存在的,并且对该key并发请求量很大,就会对后端系统造成很大的压力。
也就是说,对不存在的key进行高并发访问,导致数据库压力瞬间增大,这就叫做【缓存穿透】。
解决方案: 对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存。
缓存雪崩 {#缓存雪崩}
当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力。
突然间大量的key失效了或redis重启,大量访问数据库而导致的系统压力剧增问题就是缓存雪崩啦。
解决方案:
- key的失效期分散开,不同的key设置不同的有效期;
- 设置二级缓存;
- 高可用方案,比如redis集群,保证不会因为缓存系统崩溃而导致缓存雪崩;
缓存击穿 {#缓存击穿}
对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常"热点"的数据。这个时候,需要考虑一个问题:缓存被"击穿"的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。
缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
解决方案:
-
用分布式锁控制访问的线程,如:使用redis的setnx互斥锁先进行判断,这样其他线程就处于等待状态,保证不会有大并发操作去操作数 据库。
|-----------|------------------------------------------------| |
1
| if(redis.sexnx()==1){ //先查询缓存 //查询数据库 //加入缓存 } |
缓存双写一致性 {#缓存双写一致性}
这个问题是业务开发中常见的问题,那就是在写redis和数据库时如何保证数据的一致性呢? 如果你还不了解什么情况下会出现双写一致性问题,那么下面我来举几个例子,大家看看是不是很熟悉?你们平常是不是就是这么来使用redis的呢?
先更新数据库再更新缓存(不建议使用); {#先更新数据库再更新缓存不建议使用}
操作步骤(线程A和线程B都对同一数据进行更新操作):
- 线程A更新了数据库
- 线程B更新了数据库
- 线程B更新了缓存
- 线程A更新了缓存
显而易见,这面这种操作的问题在于:脏读、浪费性能
先更新数据库再删除缓存 {#先更新数据库再删除缓存}
操作步骤:
- 请求A进行写操作,删除缓存,此时A的写操作还没有执行完
- 请求B查询发现缓存不存在
- 请求B去数据库查询得到旧值
- 请求B将旧值写入缓存
- 请求A将新值写入数据库
此方案,可以看到在步骤4会将旧值最后写入缓存,最终造成脏读;
先删除缓存再更新数据库 {#先删除缓存再更新数据库}
操作步骤:
- 用户A删除缓存失败
- 用户A成功更新了数据
或者:
- 用户A删除了缓存;
- 用户B读取缓存,缓存不存在;
- 用户B从数据库拿到旧数据;
- 用户B更新了缓存;
- 用户A更新了数据;
按照上面的步骤,此方案也是会出现脏读问题,导致数据双写不一致而引发业务系统异常。
这里给出几种解决方案:
- 解决方案1:设置缓存有效时间(最简单),在接受最终一致性的场景下,配置合理的失效时间。
- 解决方案2:使用消息队列,例如rocketMq等消息队列可以保证数据操作顺序一致性,确保缓存系统的数据正常。
这就是Redis缓存系统经常问的 缓存击穿、缓存雪崩、缓存击穿 之间的区别,虽然这几个概念听着非常的相似,实质上在定义上还是有一些细微的区别。同时也介绍了在业务系统使用中常见的 数据双写缓存一致性 问题的场景和推荐的解决方案,希望能帮助到大家。
感谢各位小伙伴的:点赞、收藏和评论,我们下期更精彩!