前面一节说到了《为什么说Redis是单线程的以及Redis为什么这么快!》,今天给大家整理一篇关于Redis经常被问到的问题:缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级等概念的入门及简单解决方案。

一、缓存雪崩

缓存雪崩我们可以简单的理解为:由于原有缓存失效,新缓存未到期间(例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。

缓存正常从Redis中获取,示意图如下:

缓存失效瞬间示意图如下:

缓存失效时的雪崩效应对底层系统的冲击非常可怕!大多数系统设计者考虑用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。还有一个简单方案就时讲缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

以下简单介绍两种实现方式的伪代码:

(1)碰到这种情况,一般并发量不是特别多的时候,使用最多的解决方案是加锁排队,伪代码如下:

//伪代码
public object GetProductListNew() {int cacheTime = 30;String cacheKey = "product_list";String lockKey = cacheKey;String cacheValue = CacheHelper.get(cacheKey);if (cacheValue != null) {return cacheValue;} else {synchronized(lockKey) {cacheValue = CacheHelper.get(cacheKey);if (cacheValue != null) {return cacheValue;} else {//这里一般是sql查询数据cacheValue = GetProductListFromDB(); CacheHelper.Add(cacheKey, cacheValue, cacheTime);}}return cacheValue;}
}

加锁排队只是为了减轻数据库的压力,并没有提高系统吞吐量。假设在高并发下,缓存重建期间key是锁着的,这是过来1000个请求999个都在阻塞的。同样会导致用户等待超时,这是个治标不治本的方法!

注意:加锁排队的解决方式分布式环境的并发问题,有可能还要解决分布式锁的问题;线程还会被阻塞,用户体验很差!因此,在真正的高并发场景下很少使用!

(2)还有一个解决办法解决方案是:给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存,实例伪代码如下:

//伪代码
public object GetProductListNew() {int cacheTime = 30;String cacheKey = "product_list";//缓存标记String cacheSign = cacheKey + "_sign";String sign = CacheHelper.Get(cacheSign);//获取缓存值String cacheValue = CacheHelper.Get(cacheKey);if (sign != null) {return cacheValue; //未过期,直接返回} else {CacheHelper.Add(cacheSign, "1", cacheTime);ThreadPool.QueueUserWorkItem((arg) -> {//这里一般是 sql查询数据cacheValue = GetProductListFromDB(); //日期设缓存时间的2倍,用于脏读CacheHelper.Add(cacheKey, cacheValue, cacheTime * 2);                 });return cacheValue;}
}

解释说明:

1、缓存标记:记录缓存数据是否过期,如果过期会触发通知另外的线程在后台去更新实际key的缓存;

2、缓存数据:它的过期时间比缓存标记的时间延长1倍,例:标记缓存时间30分钟,数据缓存设置为60分钟。 这样,当缓存标记key过期后,实际缓存还能把旧数据返回给调用端,直到另外的线程在后台更新完成后,才会返回新缓存。

关于缓存崩溃的解决方法,这里提出了三种方案:使用锁或队列、设置过期标志更新缓存、为key设置不同的缓存失效时间,还有一各被称为“二级缓存”的解决方法,有兴趣的读者可以自行研究。

二、缓存穿透

缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空(相当于进行了两次无用的查询)。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。

有很多种方法可以有效地解决缓存穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。

另外也有一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。通过这个直接设置的默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库,这种办法最简单粗暴!

//伪代码
public object GetProductListNew() {int cacheTime = 30;String cacheKey = "product_list";String cacheValue = CacheHelper.Get(cacheKey);if (cacheValue != null) {return cacheValue;}cacheValue = CacheHelper.Get(cacheKey);if (cacheValue != null) {return cacheValue;} else {//数据库查询不到,为空cacheValue = GetProductListFromDB();if (cacheValue == null) {//如果发现为空,设置个默认值,也缓存起来cacheValue = string.Empty;}CacheHelper.Add(cacheKey, cacheValue, cacheTime);return cacheValue;}
}

把空结果,也给缓存起来,这样下次同样的请求就可以直接返回空了,即可以避免当查询的值为空时引起的缓存穿透。同时也可以单独设置个缓存区域存储空值,对要查询的key进行预先校验,然后再放行给后面的正常缓存处理逻辑。

三、缓存预热

缓存预热这个应该是一个比较常见的概念,相信很多小伙伴都应该可以很容易的理解,缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!

解决思路:

1、直接写个缓存刷新页面,上线时手工操作下;

2、数据量不大,可以在项目启动的时候自动进行加载;

3、定时刷新缓存;

四、缓存更新

除了缓存服务器自带的缓存失效策略之外(Redis默认的有6中策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种:

(1)定时去清理过期的缓存;

(2)当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。

两者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂!具体用哪种方案,大家可以根据自己的应用场景来权衡。

五、缓存降级

当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。

降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。

在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅;从而梳理出哪些必须誓死保护,哪些可降级;比如可以参考日志级别设置预案:

(1)一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;

(2)警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;

(3)错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;

(4)严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。

六、总结

这些都是实际项目中,可能碰到的一些问题,也是面试的时候经常会被问到的知识点,实际上还有很多很多各种各样的问题,文中的解决方案,也不可能满足所有的场景,相对来说只是对该问题的入门解决方法。一般正式的业务场景往往要复杂的多,应用场景不同,方法和解决方案也不同,由于上述方案,考虑的问题并不是很全面,因此并不适用于正式的项目开发,但是可以作为概念理解入门,具体解决方案要根据实际情况来确定!


参考文章:

1、http://www.cnblogs.com/zhangweizhong/p/6258797.html
2、http://www.cnblogs.com/zhangweizhong/p/5884761.html
3、http://blog.csdn.net/zeb_perfect/article/details/54135506

【视频福利】2T免费学习视频,搜索或扫描上述二维码关注微信公众号:Java后端技术(ID: JavaITWork)回复:1024,即可免费获取!内含SSM、Spring全家桶、微服务、MySQL、MyCat、集群、分布式、中间件、Linux、网络、多线程,Jenkins、Nexus、Docker、ELK等等免费学习视频,持续更新!

缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级等问题相关推荐

  1. 小白也能看懂的缓存雪崩、穿透、击穿

    作为后端开发,我想缓存是大家再熟悉不过的东西了. 我会介绍出现缓存雪崩.穿透和击穿的业务背景.解决方案和对业务可靠性处理.事先说明,最佳解决方案一定需要结合实际业务调整,不同业务的处理不完全相同 其实 ...

  2. 顶级“Redis学习笔记”,缓存雪崩+击穿+穿透+集群+分布式锁,NB了

    如果你是一位后端工程师,面试时八成会被问到 Redis,特别是那些大型互联网公司,不仅要求面试者能简单使用 Redis,还要深入理解其底层实现原理,具备解决常见问题的能力.可以说,熟练使用 Redis ...

  3. redis缓存雪崩、穿透、击穿概念、布隆过滤器小结及解决办法

    判存业务 redis缓存雪崩.穿透.击穿概念及解决办法 什么是 概念: 1.缓存雪崩 对于系统 A,假设每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意 ...

  4. Redis缓存穿透、击穿、雪崩、预热、更新、降级

    Redis是高性能的分布式内存数据库,对于内存数据库经常会出现下面几种情况,也经常会出现在Redis面试题中:缓存穿透.缓存击穿.缓存雪崩.缓存预热.缓存更新.缓存降级.本篇分别介绍这些概念以及对应的 ...

  5. 解决Redis缓存雪崩、穿透、并发、预热、击穿、热点Key等问题

    一.缓存雪崩 数据未加载到缓存中,或者缓存同一时间大面积的失效,从而导致所有请求都去查数据库,导致数据库CPU和内存负载过高,甚至宕机. 比如一个雪崩的简单过程: 1.redis集群大面积故障 2.缓 ...

  6. 【Redis】快速掌握:缓存雪崩、穿透、击穿、预热

    目录 前言 一.缓存 1.1.程序中缓存是什么样的? 1.2.缓存的优点 1.3.缓存的分类 二.缓存特性 2.1.缓存雪崩 2.1.1.雪崩问题 2.1.2.如何解决缓存雪崩问题 2.2.缓存穿透 ...

  7. 轻松易懂的缓存雪崩、穿透、击穿以及解决方案

    缓存雪崩 先来看下雪崩的过程 所谓缓存雪崩,指的是缓存数据同一时间大量失效,所有的请求全打到数据库,导致数据库在巨大压力下挂掉. 比如在双十一的时候,用户都会打开淘宝买东西,有的人是真的有需要买的,有 ...

  8. 缓存雪崩 击穿 穿透

    缓存穿透 **缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意 ...

  9. Redis缓存雪崩、穿透、击穿,布隆过滤器,分布式锁详解

    缓存雪崩 在某一个时间存在大量的缓存key失效 解决办法 1.有效期一直---->给每一个数据加上水机有效期 2.redis挂掉了----->使用redis集群,分摊key的存储 引出re ...

  10. 阿里面试Redis最常问的三个问题:缓存雪崩、击穿、穿透(带答案)

    你知道的越多,你不知道的越多 点赞再看,养成习惯 https://github.com/java-已经开源,有面试脑图 正文 上一期吊打系列我们提到了Redis的基础知识,还没看的小伙伴可以回顾一下 ...

最新文章

  1. 2021年大数据Spark(九):Spark On Yarn两种模式总结
  2. Image Filters for IOS
  3. 每日一皮:当产品经理试图让程序员冷静下来的时候...
  4. Android中的网格布局
  5. hibernate教程--抓取策略
  6. oracle expdp ora39070,expdp时出错:ORA-39070: Unable to open the log file.
  7. 【Transformer】视觉Transformer最新进展——持续更新
  8. easypoi 导入失败返回错误文件_从Excel批量导入数据说到ForkJoin的原理
  9. 汇编语言复习摘要六——包含多个段的程序
  10. 华为正式发布鸿蒙OS操作系统,分布式架构首次用于终端
  11. 【java】输出素数
  12. python模拟登录12306_python基于selenium模块实现自动登录12306网站
  13. 面试宝典-常见面试问题回答技巧
  14. 十年老码农,现场教你写简历!
  15. Spring读源码系列番外篇04----类型转换--上---老旧的PropertyEditor
  16. 319. 灯泡开关【我亦无他唯手熟尔】
  17. 王子恢:手机视频牌照与行政权力租界
  18. JAVA基础学习博客007
  19. Excel进度条设置百分比解决
  20. win10开机未能正确启动_设置华硕电脑定时开机只需两步!

热门文章

  1. 【亲测可行】error while loading shared libraries的解决方案
  2. 本地下载配置dubbo.xsd文件
  3. springboot下载依赖包
  4. 微信小程序截屏保存图片
  5. 双足竞走机器人的意义_双足竞走机器人设计1
  6. realsense python 操作摄像头
  7. Red and Black 模板题 /// BFS oj22063
  8. 基于单片机的双足仿生运动机器人的设计
  9. 在数据库应用系统中数据库的开发
  10. 2019年秋季学期实验室安全考试1