redis缓存穿透,缓存击穿,缓存雪崩原因+解决方案

01前言

在我们日常的开发中,无不都是使用数据库来进行数据的存储,由于一般的系统任务中通常不会存在高并发的情况,所以这样看起来并没有什么问题,可是一旦涉及大数据量的需求,比如一些商品抢购的情景,或者是主页访问量瞬间较大的时候,单一使用数据库来保存数据的系统会因为面向磁盘,磁盘读/写速度比较慢的问题而存在严重的性能弊端,一瞬间成千上万的请求到来,需要系统在极短的时间内完成成千上万次的读/写操作,这个时候往往不是数据库能够承受的,极其容易造成数据库系统瘫痪,最终导致服务宕机的严重生产问题。

为了克服上述的问题,项目通常会引入NoSQL技术,这是一种基于内存的数据库,并且提供一定的持久化功能。

redis技术就是NoSQL技术中的一种,但是引入redis又有可能出现缓存穿透,缓存击穿,缓存雪崩等问题。本文就对这三种问题进行较深入剖析。

02初认识

  • 缓存穿透:key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会到数据源,从而可能压垮数据源。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。
  • 缓存击穿:key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
  • 缓存雪崩:当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力。

03缓存穿透解决方案

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

有很多种方法可以有效地解决缓存穿透问题最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的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;}
}

04缓存击穿解决方案

key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题。

  • 使用互斥锁(mutex key)

业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。

SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果。

public String get(key) {String value = redis.get(key);if (value == null) { //代表缓存值过期//设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能load dbif (redis.setnx(key_mutex, 1, 3 * 60) == 1) { //代表设置成功value = db.get(key);redis.set(key, value, expire_secs);redis.del(key_mutex);} else { //这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可sleep(50);get(key); //重试}} else {return value; }}

memcache代码:

if (memcache.get(key) == null) { // 3 min timeout to avoid mutex holder crash if (memcache.add(key_mutex, 3 * 60 * 1000) == true) { value = db.get(key); memcache.set(key, value); memcache.delete(key_mutex); } else { sleep(50); retry(); }
}

其它方案:待各位补充。

05缓存雪崩解决方案

与缓存击穿的区别在于这里针对很多key缓存,前者则是某一个key。

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

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

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

加锁排队,伪代码如下:

//伪代码
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个都在阻塞的。同样会导致用户等待超时,这是个治标不治本的方法!

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

随机值伪代码:

//伪代码
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;}
}

解释说明:

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

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

06小结

针对业务系统,永远都是具体情况具体分析,没有最好,只有最合适。

于缓存其它问题,缓存满了和数据丢失等问题,大伙可自行学习。最后也提一下三个词LRU、RDB、AOF,通常我们采用LRU策略处理溢出,Redis的RDB和AOF持久化策略来保证一定情况下的数据安全。

参考相关链接:

https://blog.csdn.net/zeb_perfect/article/details/54135506

https://blog.csdn.net/fanrenxiang/article/details/80542580

https://baijiahao.baidu.com/s?id=1619572269435584821&wfr=spider&for=pc

https://blog.csdn.net/xlgen157387/article/details/79530877

深度剖析redis缓存穿透,缓存击穿,缓存雪崩原因+解决方案相关推荐

  1. Redis 缓存穿透、击穿、雪崩现象及解决方案

    前言 如何有效的理解并且区分 Reids 穿透.击穿和雪崩 缓存穿透 关键词:穿过 Redis 和数据库 当 Redis 和数据库中都没有我们想要的数据时,就需要考虑缓存穿透的问题了.下面这段逻辑大家 ...

  2. Redis应用问题解决(缓存穿透、击穿、雪崩、分布式锁)

    Redis应用问题解决(缓存穿透.击穿.雪崩.分布式锁) 缓存穿透 问题描述 当系统中引入redis缓存后,一个请求进来后,会先从redis缓存中查询,缓存有就直接返回,缓存中没有就去db中查询,db ...

  3. Redis基本数据类型、持久化机制、集群模式、淘汰策略、缓存穿透、击穿、雪崩、常见面试题大集合!

    redis redis reids的常用数据类型 1.String 2.Hash 3.List 5.Sorted Set(ZSet) 6.其他 发布(pub)订阅模式(sub)模式 作用 Redis的 ...

  4. Redis缓存穿透、击穿、雪崩、概念及解决办法

    在生产环境中,会因为很多的原因造成访问请求绕过了缓存,都需要访问数据库持久层,虽然对Redsi缓存服务器不会造成影响,但是数据库的负载就会增大,使缓存的作用降低 一.缓存穿透 1.缓存穿透理解   缓 ...

  5. Redis 缓存穿透、击穿、雪崩 解决方法

    目录 引言 一.缓存穿透 1. 缓存穿透的原理 2. 解决方法 2.1 布隆过滤器 2.2 缓存空对象 二.缓存击穿 1. 缓存击穿原理 2. 解决方法 2.1 设置热点数据永不过期 2.2 加互斥锁 ...

  6. 牛逼,三句话搞懂 Redis 缓存穿透、击穿、雪崩!

    前言 如何有效的理解并且区分 Reids 穿透.击穿和雪崩之间的区别,一直以来都挺困扰我的.特别是穿透和击穿,过一段时间就稀里糊涂的分不清了. 为了有效的帮助笔者自己,以及拥有同样烦恼的朋友们区分这三 ...

  7. 缓存穿透、击穿、雪崩什么的傻傻分不清楚?看了这篇文后,我明白了

    对于缓存,大家肯定都不陌生,不管是前端还是服务端开发,缓存几乎都是必不可少的优化方式之一.在实际生产环境中,缓存的使用规范也是一直备受重视的,如果使用的不好,很容易就遇到缓存击穿.雪崩等严重异常情景, ...

  8. 缓存穿透、击穿、雪崩

    一.缓存穿透 缓存穿透是指缓存和数据库中均不存在目标数据,而用户不断发起请求,缓存也得不到更新,由此每次请求该数据都会到数据库.高并发量,就会对后端的 DB 系统造成很大压力.如查询 id 为&quo ...

  9. Redis_缓存穿透、击穿、雪崩

    查询步骤图解 1.缓存穿透 什么是缓存穿透:          查询请求一直向数据库查询,导致数据库压力过大,甚至奔溃         本质原因:查询的数据既不在缓存中,也不在数据库中 此时会出现:程 ...

  10. Redis的穿透、击穿、雪崩问题

    目录 Redis穿透 解决方法1:布隆过滤器 解决方法2:返回空对象 解决方法3:接口校验 Redis击穿 解决方案1:可以设置热点数据永远不过期 解决方案2:添加锁 Redis雪崩 解决方案1:加互 ...

最新文章

  1. 树莓派安装python3.5+tensorflow_树莓派4B安装Tensorflow的方法步骤
  2. Oracle 11g Dataguard 物理备库配置(四)之broker snapshot standby测试
  3. Python原来这么厉害,我震惊了!
  4. 一天学完spark的Scala基础语法教程十、类和对象(idea版本)
  5. css hack技巧_5种减少Hack的编码技巧
  6. OpenVINO 部署 Mask-RCNN 实例分割
  7. Codeforces 55D Beautiful numbers (数位DP)
  8. 如何在sql存储过程中打log_教你如何记录Entity Framework框架自动生成的SQL语句
  9. rs232 距离_串行通信接口之一RS232接口
  10. 光线跟踪是什么? 光能传递(radiosity lighting)是什么?
  11. 正点原子STM32学习笔记——MPU6050介绍
  12. 基于SSM+SpringBoot+Thymeleaf+LayUI的高校大学生成绩分析管理系统(附论文)
  13. 如何美化菜单界面java_用 Java 创建带图像的菜单来美化界面
  14. python中的in和out是什么意思
  15. python人工智能应用锁_自兴人工智能——Python 第一课
  16. 用C++实现强化学习,速度不亚于Python,这里有个框架可用
  17. 一文精通S4 HANA中的Business Partner
  18. 移动硬盘出现好多类似5b823bbe980da233a005c83a\update的文件,0字节,删不掉
  19. HTML我的家乡宁夏学生网页设计作品 dreamweaver作业静态HTML网页设计模板 宁夏旅游景点网页作业制作...
  20. Asus EeePC X101上网本为MeeGo带来新的生机

热门文章

  1. 2018,扬帆起航!
  2. sicp 3.9题解答
  3. 算法分析-堆排序 HeapSort 优先级队列
  4. 第十五周项目3-在OJ上玩指针
  5. lamp+cacti+ntop+thold+nagios+syslog
  6. MAC安装软件 brew 问题记录
  7. Android 存储学习之保存系统短信到SD卡
  8. Virtual Routing and Forwarding
  9. PJSIP在windows(xp或者win7)下的编译,编译工具是vs2008,PJSIP版本2.3
  10. maven内置属性详细说明