【业务功能篇87】微服务-springcloud-本地缓存-redis-分布式缓存-缓存穿透-雪崩-击穿

news2024/11/19 19:58:51

一、缓存

1. 什么是缓存

  缓存的作用是减低对数据源的访问频率。从而提高我们系统的性能。

image.png

image.png

缓存的流程图

image.png

2.缓存的分类

2.1 本地缓存

  其实就是把缓存数据存储在内存中(Map <String,Object>).在单体架构中肯定没有问题。

image.png

单体架构下的缓存处理

image.png

2.2 分布式缓存

  在分布式环境下,我们原来的本地缓存就不是太使用了,原因是:

  • 缓存数据冗余
  • 缓存效率不高

image.png

  分布式缓存的结构图

image.png

3.整合Redis

  要整合Redis那么我们在SpringBoot项目中首页来添加对应的依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

  然后我们需要添加对应的配置信息

image.png

测试操作Redis的数据

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Test
    public void testStringRedisTemplate(){
        // 获取操作String类型的Options对象
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
        // 插入数据
        ops.set("name","bobo"+ UUID.randomUUID());
        // 获取存储的信息
        System.out.println("刚刚保存的值:"+ops.get("name"));
    }

查看可以通过Redis的客户端连接查看

image.png

也可以通过工具查看

image.png

4.改造三级分类

  在首页查询二级和三级分类数据的时候我们可以通过Redis来缓存存储对应的数据,来提升检索的效率。

@Override
    public Map<String, List<Catalog2VO>> getCatelog2JSON() {
        // 从Redis中获取分类的信息
        String catalogJSON = stringRedisTemplate.opsForValue().get("catalogJSON");
        if(StringUtils.isEmpty(catalogJSON)){
            // 缓存中没有数据,需要从数据库中查询
            Map<String, List<Catalog2VO>> catelog2JSONForDb = getCatelog2JSONForDb();
            // 从数据库中查询到的数据,我们需要给缓存中也存储一份
            String json = JSON.toJSONString(catelog2JSONForDb);
            stringRedisTemplate.opsForValue().set("catalogJSON",json);
            return catelog2JSONForDb;
        }
        // 表示缓存命中了数据,那么从缓存中获取信息,然后返回
        Map<String, List<Catalog2VO>> stringListMap = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catalog2VO>>>() {
        });
        return stringListMap;
    }

  然后对三级分类的数据做压力测试

压力测试内容压力测试的线程数吞吐量/s90%响应时间99%响应时间
Nginx507,3851070
Gateway5023,170314
单独测试服务5023,16037
Gateway+服务508,4611246
Nginx+Gateway50
Nginx+Gateway+服务502,8162742
一级菜单501,3214874
三级分类压测501240004000
三级分类压测(业务优化后)50448113227
三级分类压测(Redis缓存)5011634959

  通过对比可以看到Redis缓存加入后的性能提升的效果还是非常明显的。

image.png

5.缓存穿透

  指查询一个一定不存在的数据,由于缓存是不命中,将去查询数据库,但是数据库也无此记录,我们没有将这次查询的null写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义.

image.png

利用不存在的数据进行攻击,数据库瞬时压力增大,最终导致崩溃,解决方案也比较简单,直接把null结果缓存,并加入短暂的过期时间

image.png

6.缓存雪崩

  缓存雪崩是指在我们设置缓存时key采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。

image.png

解决方案:原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
注意这里的随机数要取正数,这里有可能随机出负数,那么有效期时间就是无效的会报异常

image.png

7.缓存击穿

  对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。如果这个key在大量请求同时进来前正好失效,那么所有对这个key的数据查询都落到db,我们称为缓存击穿。

image.png

解决方案:加锁,大量并发只让一个去查,其他人等待,查到以后释放锁,其他人获取到锁,先查缓存,就会有数据,不用去db。

image.png

但是当我们压力测试的时候,输出的结果有点出乎我们的意料

image.png

做了两次的查询,原因是释放锁和查询结果缓存的时序问题

image.png

我们只需要调整下释放锁和结果缓存的时序问题就可以了

image.png

然后就是完整的代码处理

/**
     * 查询出所有的二级和三级分类的数据
     * 并封装为Map<String, Catalog2VO>对象
     * @return
     */
    @Override
    public Map<String, List<Catalog2VO>> getCatelog2JSON() {
        String key = "catalogJSON";
        // 从Redis中获取分类的信息
        String catalogJSON = stringRedisTemplate.opsForValue().get(key);
        if(StringUtils.isEmpty(catalogJSON)){
            System.out.println("缓存没有命中.....");
            // 缓存中没有数据,需要从数据库中查询
            Map<String, List<Catalog2VO>> catelog2JSONForDb = getCatelog2JSONForDb();
            if(catelog2JSONForDb == null){
                // 那就说明数据库中也不存在  防止缓存穿透
                stringRedisTemplate.opsForValue().set(key,"1",5, TimeUnit.SECONDS);
            }else{
                // 从数据库中查询到的数据,我们需要给缓存中也存储一份
                // 防止缓存雪崩
                String json = JSON.toJSONString(catelog2JSONForDb);
                stringRedisTemplate.opsForValue().set("catalogJSON",json,10,TimeUnit.MINUTES);
            }

            return catelog2JSONForDb;
        }
        System.out.println("缓存命中了....");
        // 表示缓存命中了数据,那么从缓存中获取信息,然后返回
        Map<String, List<Catalog2VO>> stringListMap = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catalog2VO>>>() {
        });
        return stringListMap;
    }

    /**
     * 从数据库查询的结果
     * 查询出所有的二级和三级分类的数据
     * 并封装为Map<String, Catalog2VO>对象
     * 在SpringBoot中,默认的情况下是单例
     * @return
     */
    public Map<String, List<Catalog2VO>> getCatelog2JSONForDb() {
        String keys = "catalogJSON";
        synchronized (this){
            /*if(cache.containsKey("getCatelog2JSON")){
                // 直接从缓存中获取
                return cache.get("getCatelog2JSON");
            }*/
            // 先去缓存中查询有没有数据,如果有就返回,否则查询数据库
            // 从Redis中获取分类的信息
            String catalogJSON = stringRedisTemplate.opsForValue().get(keys);
            if(!StringUtils.isEmpty(catalogJSON)){
                // 说明缓存命中
                // 表示缓存命中了数据,那么从缓存中获取信息,然后返回
                Map<String, List<Catalog2VO>> stringListMap = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catalog2VO>>>() {
                });
                return stringListMap;
            }
            System.out.println("-----------》查询数据库操作");

            // 获取所有的分类数据
            List<CategoryEntity> list = baseMapper.selectList(new QueryWrapper<CategoryEntity>());
            // 获取所有的一级分类的数据
            List<CategoryEntity> leve1Category = this.queryByParenCid(list,0l);
            // 把一级分类的数据转换为Map容器 key就是一级分类的编号, value就是一级分类对应的二级分类的数据
            Map<String, List<Catalog2VO>> map = leve1Category.stream().collect(Collectors.toMap(
                    key -> key.getCatId().toString()
                    , value -> {
                        // 根据一级分类的编号,查询出对应的二级分类的数据
                        List<CategoryEntity> l2Catalogs = this.queryByParenCid(list,value.getCatId());
                        List<Catalog2VO> Catalog2VOs =null;
                        if(l2Catalogs != null){
                            Catalog2VOs = l2Catalogs.stream().map(l2 -> {
                                // 需要把查询出来的二级分类的数据填充到对应的Catelog2VO中
                                Catalog2VO catalog2VO = new Catalog2VO(l2.getParentCid().toString(), null, l2.getCatId().toString(), l2.getName());
                                // 根据二级分类的数据找到对应的三级分类的信息
                                List<CategoryEntity> l3Catelogs = this.queryByParenCid(list,l2.getCatId());
                                if(l3Catelogs != null){
                                    // 获取到的二级分类对应的三级分类的数据
                                    List<Catalog2VO.Catalog3VO> catalog3VOS = l3Catelogs.stream().map(l3 -> {
                                        Catalog2VO.Catalog3VO catalog3VO = new Catalog2VO.Catalog3VO(l3.getParentCid().toString(), l3.getCatId().toString(), l3.getName());
                                        return catalog3VO;
                                    }).collect(Collectors.toList());
                                    // 三级分类关联二级分类
                                    catalog2VO.setCatalog3List(catalog3VOS);
                                }
                                return catalog2VO;
                            }).collect(Collectors.toList());
                        }

                        return Catalog2VOs;
                    }
            ));
            // 从数据库中获取到了对应的信息 然后在缓存中也存储一份信息
            //cache.put("getCatelog2JSON",map);
            // 表示缓存命中了数据,那么从缓存中获取信息,然后返回
            if(map == null){
                // 那就说明数据库中也不存在  防止缓存穿透
                stringRedisTemplate.opsForValue().set(keys,"1",5, TimeUnit.SECONDS);
            }else{
                // 从数据库中查询到的数据,我们需要给缓存中也存储一份
                // 防止缓存雪崩
                String json = JSON.toJSONString(map);
                stringRedisTemplate.opsForValue().set("catalogJSON",json,10,TimeUnit.MINUTES);
            }
            return map;
        } }

8.本地锁的局限

  本地锁在分布式环境下,是没有办法锁住其他节点的操作的,这种情况肯定是有问题的

image.png

针对本地锁的问题,我们需要通过分布式锁来解决,那么是不是意味着本身锁在分布式场景下就不需要了呢?

image.png

  显然不是这样的,因为如果分布式环境下的每个节点不控制请求的数量,那么分布式锁的压力会非常大,这时我们需要本地锁来控制每个节点的同步,来降低分布式锁的压力,所以实际开发中我们都是本地锁和分布式锁结合使用的

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Iterator: hasNext()、next()、remove()

一、Iterator的API 关于Iterator主要有三个方法&#xff1a;hasNext()、next()、remove() hasNext:没有指针下移操作&#xff0c;只是判断是否存在下一个元素next&#xff1a;指针下移&#xff0c;返回该指针所指向的元素remove&#xff1a;删除当前指针所指向的元素&#xf…

树莓派 SSD1306

树莓派安装python3.9以及pip换源_树莓派安装pip_&#xff2c;&#xff2a;&#xff38;的博客-CSDN博客 树莓派使用 Python 驱动 SSD1306&#xff08;IIC/SPI 通信&#xff09; 进阶篇——树莓派OLED模块的使用 大量例程详解_oled例程_玩转智能机器人的博客-CSDN博客 使用OS 版…

从零开始学习盲盒小程序开发

在当前的电商市场中&#xff0c;微信盲盒小程序以其独特的互动性和惊喜感&#xff0c;已经成为吸引消费者的一种新兴电商模式。如果你也想搭建自己的微信盲盒小程序&#xff0c;本文将详细介绍如何从零开始实现这一目标。 第一步&#xff1a;登录【乔拓云】网后台&#xff0c;进…

自然语言处理2-NLP

目录 自然语言处理2-NLP 如何把词转换为向量 如何让向量具有语义信息 在CBOW中 在Skip-gram中 skip-gram比CBOW效果更好 CBOW和Skip-gram的算法实现 Skip-gram的理想实现 Skip-gram的实际实现 自然语言处理2-NLP 在自然语言处理任务中&#xff0c;词向量&#xff08;…

利用fsimage分析HDFS小文件

一、Hive 小文件概述 在Hive中&#xff0c;所谓的小文件是指文件大小远小于HDFS块大小的文件&#xff0c;通常小于128 MB&#xff0c;甚至更少。这些小文件可能是Hive表的一部分&#xff0c;每个小文件都包含一个或几个表的记录&#xff0c;它们以文本格式存储。 Hive通常用于…

卫星网络中的量子通信

当今社会&#xff0c;通信已经成为人类生活中不可或缺的一部分&#xff0c;而随着科技的迅猛发展&#xff0c;我们的通信方式也在不断革新和进化。近年来&#xff0c;量子通信作为一项引人瞩目的领域&#xff0c;正逐渐走入人们的视野。与传统通信方式相比&#xff0c;量子通信…

渗透测试漏洞原理之---【任意文件上传漏洞】

文章目录 1、任意文件上传概述1.1、漏洞成因1.2、漏洞危害 2、WebShell解析2.1、Shell2.2、WebShell2.2.1、大马2.2.2、小马2.2.3、GetShell 3、任意文件上传攻防3.1、毫无检测3.1.1、源代码3.1.2、代码审计3.1.3、靶场试炼 3.2、黑白名单策略3.2.1、文件检测3.2.2、后缀名黑名…

【AI】数学基础——线代(矩阵特征值,特征向量矩阵分解)

【AI】数学基础——线代&#xff08;向量部分&#xff09; 文章目录 2.3 矩阵2.3.1 二元方程组求解与行列式行列式 2.3.2 用矩阵形式表示数据矩阵与行列式区别特殊矩阵 2.3.3 矩阵的秩矩阵的秩 2.3.4 矩阵运算加减法数乘运算矩阵乘向量线性变换角度线性组合角度 矩阵乘矩阵转置…

MySQL有哪些常见的存储引擎?

主要存储引擎 主要的三个存储引擎MyISAMMemoryInnoDB 那存储引擎应该怎么选择&#xff1f;InnoDB 和 MylSAM 主要有什么区别&#xff1f; MySQL有9种存储引擎&#xff0c;不同的引擎&#xff0c;适合不同的场景&#xff0c;我们最常用的&#xff0c;可能就是InnoDB&#xff0c;…

向前兼容与向后兼容

2012年3月发布了Go 1.0,随着 Go 第一个版本发布的还有一份兼容性说明文档。该文档说明&#xff0c;Go 的未来版本会确保向后兼容性&#xff0c;不会破坏现有程序。 即用10年前Go 1.0写的代码&#xff0c;用10年后的Go 1.18版本&#xff0c;依然可以正常运行。即较高版本的程序能…

泰凌微科创板成功上市

2023年8月25日&#xff0c;泰凌微电子&#xff08;上海&#xff09;股份有限公司&#xff08;股票简称“泰凌微”&#xff0c;股票代码为“688591.SH”&#xff09;成功于上海证券交易所科创板挂牌上市。自泰凌微成立以来&#xff0c;便一直致力于研发具有自主知识产权、国际一…

Matter 设备配网流程 ---- 配网材料和 SPAKE2P 机制

Matter 设备配网流程 ---- 配网材料和 SPAKE2P 机制 1. Matter 配网材料 Matter 配网&#xff08;commissioning&#xff09;使用 SPAKE2P 协议完成 PASE&#xff0c;进而验证 DAC&#xff08;Device Attestation Credentials&#xff09;&#xff0c;派发 NOC&#xff0c;然…

ubuntu入门01——windows上直接部署linux(WSL)

win10安装参考如下教程&#xff1a; 旧版 WSL 的手动安装步骤 | Microsoft Learn 说明&#xff1a;该文档是我按如上教程安装使用Ubuntu写的回顾&#xff0c;家人们参考官方教程更妙。 1.启用适用于Linux的wundows子系统 2.启用虚拟机功能 dism.exe /online /enable-feat…

Java IO操作——BufferedReader

BufferReader Java IO操作——BufferedReader(缓冲区读取内容&#xff0c;避免中文乱码) 要点&#xff1a; 掌握BufferedReader类的使用 掌握键盘输入的基本形式 Buffer&#xff1a;表示缓冲区的。之前的StringBuffer&#xff0c;缓冲区中的内容可以更改&#xff0c;可以提高…

递归算法学习——子集

目录 一&#xff0c;题目解析 二&#xff0c;例子 三&#xff0c;题目接口 四&#xff0c;解题思路以及代码 1.完全深度搜索 2.广度搜索加上深度优先搜索 五&#xff0c;相似题 1.题目 2.题目接口 3.解题代码 一&#xff0c;题目解析 给你一个整数数组 nums &#xff0c…

合宙Air724UG LuatOS-Air LVGL API控件--复选框 (Checkbox)

复选框 (Checkbox) 复选框主要是让用户进行一些内容选择&#xff0c;或者同意用户协议。 示例代码 – 复选框回调函数 function event_handler(obj, event) if event lvgl.EVENT_VALUE_CHANGED then print(“State”, lvgl.checkbox_is_checked(obj)) end end – 创建复选框…

python学习1之安装

前言 目前&#xff0c;Python有两个版本&#xff0c;一个是2.x版&#xff0c;一个是3.x版&#xff0c;这两个版本是不兼容的。由于3.x版越来越普及&#xff0c;我们的教程将以最新的Python 3.9版本为基础。 1、下载 官网地址 https://www.python.org/downloads/ 2、安装 点击…

并发 04(Callable,CountDownLatch)详细讲解

并发 Callable 1 可以返回值 2可以抛出异常 泛型指的是返回值的类型 public class Send {public static void main(String[] args) {//怎么启动Callable//new Thread().start();Aaa threadnew Aaa();FutureTask futureTasknew FutureTask(thread);new Thread(futureTask,&qu…

这款自研RedCap网关终端,获评优秀!

近日&#xff0c;由通信世界全媒体主办的“5GRedCap技术与物联网应用创新研讨会”在北京举办。作为中国电信5GInside合作计划的重要成果&#xff0c;天翼物联自主研发的“RedCap网关终端CTW-GW-01”产品获“5GRedCap优秀产品和解决方案”。此次会议&#xff0c;天翼物联行业总监…

pyqt5 QuickStart

在使用pyqt5之前&#xff0c;建议下载一个Anaconda环境&#xff0c;这样下载python包更方便&#xff0c;本篇文章是建立在已经安装好Anaconda的情况下使用的。IDE就是标准的PyCharm了。 一、pyqt包的安装 python终端执行下面两个安装命令&#xff1a; pip install PyQt5 pip …