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

一、缓存

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java学习笔记31——字符流

字符流 字符流为什么出现字符流编码表字符串中的编码解码问题字符流写数据的5中方式字符流读数据的两种方式字符流复制Java文件 字符流 为什么出现字符流 汉字的存储如果是GBK编码占用2个字节&#xff0c;如果是UTF-8占用三个字节 用字节流复制文本文件时&#xff0c;文本文…

2023年腾讯云轻量应用服务器优缺点大全

2023年腾讯云轻量应用服务器优缺点大全&#xff0c;腾讯云轻量应用服务器性能如何&#xff1f;轻量服务器CPU内存带宽配置高&#xff0c;CPU采用什么型号主频多少&#xff1f;轻量应用服务器会不会比云服务器CVM性能差&#xff1f;腾讯云服务器网详解CPU型号主频、内存、公网带…

Linux通过libudev获取挂载路径、监控U盘热拔插事件、U盘文件系统类型

文章目录 获取挂载路径监控U盘热拔插事件libusb 文件系统类型通过挂载点获取挂载路径添libudev加库 获取挂载路径 #include <stdio.h> #include <libudev.h> #include <string.h>int main() {struct udev *udev;struct udev_enumerate *enumerate;struct ud…

数据库备份和Shell基础测试及AWK(运维)

第一题&#xff1a;简述一下如何用mysql命令进行备份和恢复&#xff0c;请以test库为例&#xff0c;创建一个备份&#xff0c;并再用此备份恢复备份 备份步骤&#xff1a; 备份test库&#xff1a;使用mysqldump命令备份test库&#xff0c;并将备份写入一个.sql文件中。命令示例…

【第1章 数据结构概述】

目录 一. 基本概念 1. 数据、数据元素、数据对象 2. 数据结构 二. 数据结构的分类 1. 数据的逻辑结构可分为两大类&#xff1a;a. 线性结构&#xff1b;b. 非线性结构 2. 数据的存储结构取决于四种基本的存储方法&#xff1a;顺序存储、链接存储、索引存储、散列存储 3. …

【力扣每日一题】2023.8.24 统计参与通信的服务器

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 题目顾名思义&#xff0c;要我们统计参与通信的服务器&#xff0c;给我们一个二维矩阵&#xff0c;元素为1的位置则表示是一台服务器。 …

学习Linux基础知识与命令行操作

开始学习Linux系统前&#xff0c;首先要掌握计算机基础知识&#xff0c;了解硬件、操作系统、文件系统、网络和安全等概念。对这些基础知识的了解能够帮助理解Linux系统的概念和功能。 在Linux系统中&#xff0c;文件和目录是数据管理的基本单位。每个文件和目录都有一个称为&…

OAuth2.0 知识点梳理

文章目录 OAuth2.0 知识点梳理一、四种角色二、四种模式的概述三、四种模式的图解 OAuth2.0 知识点梳理 一、四种角色 为了能够更好的理解本文中后续的内容&#xff0c;这里我先说下&#xff0c;OAuth2.0 中相关的四种角色&#xff0c;如下&#xff1a; 资源拥有者资源服务客…

内网实战1

1、信息收集&#xff1a; 使用nmap做端口扫描&#xff1a; nmap -sV -Pn -T4 192.168.26.174重要端口&#xff1a;80、445、139、135、3306 目录扫描&#xff1a; 访问80端口&#xff1a;发现一个网站是phpstudy搭建的&#xff1b; 发现一个mysql数据库&#xff0c;那我们…

[QT]设置程序仅打开一个,再打开就唤醒已打开程序的窗口

需求&#xff1a;speedcrunch 这个软件是开源的计算器软件。配合launch类软件使用时&#xff0c;忘记关闭就经常很多窗口&#xff0c;强迫症&#xff0c;从网上搜索对版本进行了修改。 #include "gui/mainwindow.h"#include <QCoreApplication> #include <…

CocosCreator3.8研究笔记(一)windows环境安装配置

一、安装Cocos 编辑器 &#xff08;1&#xff09;、下载Cocos Dashboard安装文件 Cocos 官方网站Cocos Dashboard下载地址 &#xff1a; https://www.cocos.com/creator-download9下载完成后会得到CocosDashboard-v2.0.1-win-082215.exe 安装文件&#xff0c;双击安装即可。 …

智能工厂移动式作业轻薄加固三防平板数据采集终端

在这个高度自动化和数字化的环境中&#xff0c;数据采集变得尤为重要。为了满足这个需求&#xff0c;工业三防平板数据采集终端应运而生。工业三防平板数据采集终端采用了轻量级高强度镁合金材质&#xff0c;这使得它在保持轻薄的同时具有更强的坚固性。这种材质还具有耐磨防损…

机器学习笔记之核函数再回首:Nadarya-Watson核回归python手写示例

机器学习笔记之核函数再回首——Nadaraya-Watson核回归手写示例 引言回顾&#xff1a; Nadaraya-Watson \text{Nadaraya-Watson} Nadaraya-Watson核回归通过核函数描述样本之间的关联关系使用 Softmax \text{Softmax} Softmax函数对权重进行划分将权重与相应标签执行加权运算 N…

自动化测试(三):接口自动化pytest测试框架

文章目录 1. 接口自动化的实现2. 知识要点及实践2.1 requests.post传递的参数本质2.2 pytest单元测试框架2.2.1 pytest框架简介2.2.2 pytest装饰器2.2.3 断言、allure测试报告2.2.4 接口关联、封装改进YAML动态传参&#xff08;热加载&#xff09; 2.3 pytest接口封装&#xff…

Android 绘制之文字测量

drawText() 绘制文字 绘制进度条:paint.strokeCap Paint.CAP.RONUD 线条两边样式 设置文字字体:paint.typeFace Resources.Compat.getFont(context,font) 设置加粗 paint.isFakeBoldText 设置居中: paint.setTextAlign Paint.Align.CENTER //居中, 并不是真正的居中 往…

农村农产品信息展示网站的设计与实现(论文+源码)_kaic

摘 要 随着软件技术的迅速发展,农产品信息展示的平台越来越多,传统的农产品显示方法将被计算机图形技术取代。这种网站技术主要把农产品的描述、农产品价格、农产品图片等内容&#xff0c;通过计算机网络的开发技术&#xff0c;在互联网上进行展示&#xff0c;然后通过计算机网…

Win11共享文件,能发现主机但无法访问,提示找不到网络路径

加密长度选择如下&#xff1a; 参考以下链接&#xff1a; Redirectinghttps://answers.microsoft.com/zh-hans/windows/forum/all/win11%E8%AE%BE%E7%BD%AE%E6%96%87%E4%BB%B6%E5%A4%B9/554343a9-d963-449a-aa59-ce1e6f7c8982?tabAllReplies#tabs

小研究 - Android 字节码动态分析分布式框架(五)

安卓平台是个多进程同时运行的系统&#xff0c;它还缺少合适的动态分析接口。因此&#xff0c;在安卓平台上进行全面的动态分析具有高难度和挑战性。已有的研究大多是针对一些安全问题的分析方法或者框架&#xff0c;无法为实现更加灵活、通用的动态分析工具的开发提供支持。此…

linux字符串处理

目录 1 C 截取字符串,截取两个子串中间的字符串2 获取该字符串后面的字符串用 strstr() 函数查找需要提取的特定字符串&#xff0c;然后通过指针运算获取该字符串后面的字符串用 strtok() 函数分割字符串&#xff0c;找到需要提取的特定字符串后&#xff0c;调用 strtok() 传入…

十四五双碳双控时代下的“低碳认证”

目录 前言 十四五双碳双控时代下的“低碳认证” 一、关于“低碳认证” 二、低碳认证优势 三、环境产品认证EPD 四、EPD相关运营机构 五、碳中和相关机构 六、EPD的认证流程 七、低碳产品认证认证流程和要求 八、相关机构认证证书样例 九、证书附件表 前言 通过本篇文…