REDIS缓存穿透 击穿 雪崩

30532fde6efa4e0dbccd661b05382608.jpg一、前言

 

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

 

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

 

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

 

二、初认识

缓存穿透:key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会到数据源,从而可能压垮数据源。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。

缓存击穿:key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

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

三、缓存穿透解决方案

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

 

有很多种方法可以有效地解决缓存穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的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可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题。

 

使用互斥锁(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 db

      if (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();  

    }  

}

其它方案:待各位补充。

 

五、缓存雪崩解决方案

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

 

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

redis1.md

 

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

redis2.md

 

缓存失效时的雪崩效应对底层系统的冲击非常可怕!大多数系统设计者考虑用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。还有一个简单方案就时讲缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如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设置不同的缓存失效时间,还有一种被称为“二级缓存”的解决方法。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/5957.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vscode高亮插件——highlight-words(高亮代码、高亮变量、突出显示)

文章目录 官方教程高亮文字使用方法配置 演示变量高亮(Highlight Toggle Current)选择高亮(Highlight Selection with Options)删除高亮(Highlight Remove)侧边栏(Highlight Toggle Sidebar&…

QT——使用QListWidget、QListWidgetItem、QWidget实现自定义管理列表

作者:小 琛 欢迎转载,请标明出处 文章目录 需求场景思路描述Qt模块QListWidgetQListWidgetItem自定义QWidget配合QListWidget 例子:实现一个json文件管理窗口 需求场景 因工作需要,开发一个文件管理窗口,要让使用者可…

Buildroot 取消默认QT桌面-迅为RK3588开发板

本小节将讲解如何取消掉默认的 qt 桌面。 首先对开发板进行上电,开发板正常启动后,使用命令“cd /etc/init.d”进入到/etc/init.d 目录 下,然后使用以下命令对开机自启动脚本 rcS 进行查看,如下图所示: vi rcS 从上…

B2a实例学习记录

B2a简介 只是把hit存到了hitcollection,只是统计了各自event结果,将每次event的运行结果存起来了,并没有做总的求和 如何在B2a的基础上,实现对某一个chamber的能量的累加 1 hit和SD(sensitive detector) 每一个s…

minikube从入门到精通系列之一:部署minikube详细步骤

minikube从入门到精通系列之一:部署minikube详细步骤 一、认识Minikube二、Minikube核心知识点三、minikube官方地址四、minikube 启动五、minikube的配置需求六、Mac安装minikube七、CenotOS7上安装minikube八、启动集群九、与集群交互十、部署应用程序1.Service2.…

服务器压力测试

目录 一、磁盘性能测试安装fio磁盘性能测试工具测试内容为顺序读、随机读、顺序写、随机写、随机读写结果分析 二、CPU性能测试安装sysbench性能测试工具结果分析 三、内存性能测试结果分析 四、网络性能测试测试iperf3工具测试结果分析1.测试TCP吞吐量2 . 测试UDP丢包和延迟 此…

自定义MVC框架优化

目录 一、前言 二、优化问题 1.子控制器的初始化配置问题 2.页面跳转优化代码冗余问题 3.优化参数封装问题 三、进行优化 1.解决子控制器初始化配置 2.解决页面跳转的代码冗余问题 3.解决优化参数封装问题 4.中央控制器 一、前言 在自定义MVC框架原理中讲述了什么是…

基于matlab使用点特征匹配稳定从抖动平台捕获的视频(附源码)

一、前言 此示例演示如何稳定从抖动平台捕获的视频。稳定视频的一种方法是跟踪图像中的显著特征,并将其用作锚点以消除与其相关的所有扰动。但是,此过程必须了解第一个视频帧中这种显着特征的位置。在此示例中,我们探索了一种无需任何此类先…

亚马逊云科技中国峰会聚焦生成式AI等前沿科技,探讨当下时代的挑战与机遇

6月27日,“2023亚马逊云科技中国峰会”在上海世博中心盛大启幕! 亚马逊全球副总裁、亚马逊云科技大中华区执行董事张文翊全面阐述了在当下这个挑战与机遇并存的时代,面对生成式AI等前沿科技带来的新挑战和新机遇,企业需要“面向未…

使用 Docker 高效搭建本地开发环境(详细教程)

Docker本地开发环境的好处 试错 对开发者而言,每天会催生出的各式各样的新技术都需要尝试,然而开发者却不太可能为他们一一搭建好环境并进行测试。时间非常宝贵,正是得益于 Docker,让我们有可能在一条或者几条命令内就搭建完环境…

阿里巴巴开源Chat2DB v1.0.11 初体验

阿里巴巴开源Chat2DB v1.0.11 初体验 前言什么是Chat2DB下载安装安装配置Chat2DB初体验配置数据源准备测试数据认识几个功能菜单开始测试自然语言转SQLSQL解释SQL优化 使用总结后续功能结语 前言 作为一名阿里巴巴开源项目的拥护者,从Chat2DB开源至今都有关注这个开…

Spring 项目过程及如何使用 Spring

文章目录 1.创建 Spring 项目步骤1.1 创建 Maven 项目1.2添加 Spring 框架支持1.3 添加启动项2.如何使用 Spring2.1 存储 Bean 对象2.1.1 创建 Bean对象2.1.2 将 Bean对象注册到容器中 2.2 获取并使用 Bean对象2.2.1 使用 ApplicationContext 获取对象2.2.2 使用 BeanFactory 获…