谷粒商城【成神路】-【10】——缓存

目录

🧂1.引入缓存的优势

🥓2.哪些数据适合放入缓存 

🌭3.使用redis作为缓存组件 

🍿4.redis存在的问题 

🧈5.添加本地锁 

🥞6.添加分布式锁

🥚7.整合redisson作为分布式锁


🚗🚗🚗1.引入缓存的优势

为了系统性能的提升,我们一般都会将部分数据放入缓存中,加速访问。而db承担数据落盘工作。

🚗🚗🚗2.哪些数据适合放入缓存 

  • 即时性、数据—致性要求不高的
  • 访问量大且更新频率不高的数据(读多,写少)

🚗🚗🚗3.使用redis作为缓存组件 

先确保reidis正常启动

3.1配置redis

  • 1.引入依赖
        <!--redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
  • 2.配置reids信息
spring
  redis:
    port: 6379
    host: ip地址
    password: XXX

3.2优化查询 

之前都是从数据库查询的,现在加入缓存逻辑~

 /**
     * 使用redis缓存
     */
    @Override
    public Map<String, List<Catalog2Vo>> getCatalogJson() {

        //1.加入缓存,缓存中存的数据全都是json
        String catalogJson = redisTemplate.opsForValue().get("catalogJson");
        if (StringUtils.isEmpty(catalogJson)) {
            //2.缓存中如果没有,再去数据库查找
            Map<String, List<Catalog2Vo>> catalogJsonFromDB = getCatalogJsonFromDB();
            //3.将数据库查到的数据,将对象转换为json存放到缓存
            String s = JSON.toJSONString(catalogJsonFromDB);
            redisTemplate.opsForValue().set("catalogJson", s);
        }
        //4.从缓存中获取,转换为我们指定的类型
        Map<String, List<Catalog2Vo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {
        });

        return result;
    }

    /**
     * 从数据库查询并封装的分类数据
     *
     * @return
     */
    public Map<String, List<Catalog2Vo>> getCatalogJsonFromDB() {

        /**
         * 优化:将数据库查询的多次变为一次
         */

        List<CategoryEntity> selectList = baseMapper.selectList(null);

        //1.查出所有1级分类
        List<CategoryEntity> leve1Categorys = getParent_cid(selectList, 0L);

        //2.封装数据
        Map<String, List<Catalog2Vo>> parentCid = leve1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
            //1.每一个一级分类
            List<CategoryEntity> categoryEntities = getParent_cid(selectList, v.getCatId());
            List<Catalog2Vo> catalog2Vos = null;
            if (categoryEntities != null) {
                catalog2Vos = categoryEntities.stream().map(l2 -> {
                    Catalog2Vo vo = new Catalog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());
                    //找二级分类的三级分类
                    List<CategoryEntity> categoryEntities3 = getParent_cid(selectList, l2.getCatId());
                    if (categoryEntities3 != null) {
                        List<Catalog2Vo.Catalog3Vo> collect = categoryEntities3.stream().map(l3 -> {
                            Catalog2Vo.Catalog3Vo catalog3Vo = new Catalog2Vo.Catalog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());
                            return catalog3Vo;
                        }).collect(Collectors.toList());
                        vo.setCatalog3List(collect);
                    }
                    return vo;
                }).collect(Collectors.toList());
            }
            return catalog2Vos;
        }));

        return parentCid;
    }

3.3测试 

在本地第一次查询后,查看redis,发现redis已经存储

使用JMeter压测一下

🚗🚗🚗4.redis存在的问题 

4.1缓存穿透

  • 缓存穿透:  查询一个一定不存在的数据,由于缓存是不命中,将去查询数据库,但是数据库也无此记录,我们没有将这次查询的null写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义
  • 解决方案:null结果缓存,并加入短暂过期时间

4.2缓存雪崩 

  • 缓存雪崩: 在我们设置缓存时key采用了相同的过期时间,导致缓存在某一时刻同时失效,大量请求全部转发到DB, DB瞬时压力过重雪崩。
  • 解决方案:原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

4.3缓存击穿 

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

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

🚗🚗🚗5.添加本地锁 

若在缓存redis中没有查到,也去数据库查询,在查询数据库时,添加本地锁,在查之前,再次判断缓存reids中是否有数据,如果有,直接返回,如果没有,在查数据库,在查完数据库后,由于延迟原因,我们查完数据库时,将数据存放到缓存中,然后在释放锁

/**
     * 使用redis缓存
     */
    @Override
    public Map<String, List<Catalog2Vo>> getCatalogJson() {
        //1.使用redis缓存,存储为json对象
        String catalogJson = redisTemplate.opsForValue().get("catalogJson");
        //2.判断缓存中是否有
        if (StringUtils.isEmpty(catalogJson)) {
            System.out.println("缓存没有命中~查询数据库...");
            //3.如果缓存中没有,从数据库
            Map<String, List<Catalog2Vo>> catalogJsonFromDB = getCatalogJsonFromDB();
            return catalogJsonFromDB;
        }
        System.out.println("缓存命中....直接返回");
        //5.如果缓存中有,转换为我们需要的类型
        Map<String, List<Catalog2Vo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {
        });
        return result;
    }


    /**
     * 从数据库查询并封装的分类数据
     *
     * @return
     */
    public Map<String, List<Catalog2Vo>> getCatalogJsonFromDB() {

        /**
         * 优化:将数据库查询的多次变为一次
         */

        //TODO 本地锁,在分布式下,必须使用分布式锁
        //加锁,防止缓存击穿,使用同一把锁
        synchronized (this) {

            //加所以后,我们还要去缓存中确定一次,如果缓存中没有,才继续查数据库
            String catalogJson = redisTemplate.opsForValue().get("catalogJson");
            if (!StringUtils.isEmpty(catalogJson)) {
                //如果缓存中有,从缓存中获取
                Map<String, List<Catalog2Vo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {
                });
                return result;
            }
            System.out.println("查询了数据库~");

            List<CategoryEntity> selectList = baseMapper.selectList(null);

            //1.查出所有1级分类
            List<CategoryEntity> leve1Categorys = getParent_cid(selectList, 0L);

            //2.封装数据
            Map<String, List<Catalog2Vo>> parentCid = leve1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
                //1.每一个一级分类
                List<CategoryEntity> categoryEntities = getParent_cid(selectList, v.getCatId());
                List<Catalog2Vo> catalog2Vos = null;
                if (categoryEntities != null) {
                    catalog2Vos = categoryEntities.stream().map(l2 -> {
                        Catalog2Vo vo = new Catalog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());
                        //找二级分类的三级分类
                        List<CategoryEntity> categoryEntities3 = getParent_cid(selectList, l2.getCatId());
                        if (categoryEntities3 != null) {
                            List<Catalog2Vo.Catalog3Vo> collect = categoryEntities3.stream().map(l3 -> {
                                Catalog2Vo.Catalog3Vo catalog3Vo = new Catalog2Vo.Catalog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());
                                return catalog3Vo;
                            }).collect(Collectors.toList());
                            vo.setCatalog3List(collect);
                        }
                        return vo;
                    }).collect(Collectors.toList());
                }
                return catalog2Vos;
            }));
            //4.将从数据库中获取的数据,转换为Json存储到redis
            String s = JSON.toJSONString(parentCid);
            //设置缓存时间,方式雪崩
            redisTemplate.opsForValue().set("catalogJson", s, 1, TimeUnit.DAYS);
            return parentCid;
        }
    }

🚗🚗🚗6.添加分布式锁

使用分布式锁 步骤

  • 1.先去redis中设置一个key,为了保持原子性,同时设置过期时间
  • 2.判断是否设置成功,成功则继续业务操作,失败则自选再次获取
  • 3.执行业务之后,需要删除key释放锁,为了保持原子性,使用lua脚本
 /**
     * 使用redis缓存
     */
    @Override
    public Map<String, List<Catalog2Vo>> getCatalogJson() {
        //1.使用redis缓存,存储为json对象
        String catalogJson = redisTemplate.opsForValue().get("catalogJson");
        //2.判断缓存中是否有
        if (StringUtils.isEmpty(catalogJson)) {
            System.out.println("缓存没有命中~查询数据库...");
            //3.如果缓存中没有,从数据库
            Map<String, List<Catalog2Vo>> catalogJsonFromDB = getCatalogJsonFromDBWithRedisLock();
            return catalogJsonFromDB;
        }
        System.out.println("缓存命中....直接返回");
        //5.如果缓存中有,转换为我们需要的类型
        Map<String, List<Catalog2Vo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {
        });
        return result;
    }


    /**
     * 从数据库中获取数据,使用分布所锁
     *
     * @return
     */
    public Map<String, List<Catalog2Vo>> getCatalogJsonFromDBWithRedisLock() {
        //1.占分布式锁,去redis占位
        String uuid = UUID.randomUUID().toString();
        //2.设置过期时间,必须和加锁同步
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
        if (lock) {
            System.out.println("获取分布式锁成功..." + redisTemplate.opsForValue().get("lock"));
            //加锁成功...执行业务
            Map<String, List<Catalog2Vo>> dataFromDb;
            try {
                dataFromDb = getDataFromDb();
            } finally {
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                //删除锁,原子性
                Long lock1 = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);
            }
            return getDataFromDb();
        } else {
            //加锁失败...重试
            //休眠100毫秒
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {

            }
            return getCatalogJsonFromDBWithRedisLock();//自旋方式
        }
    }




    /**
     * 提起方法,从数据库中获取
     *
     * @return
     */
    private Map<String, List<Catalog2Vo>> getDataFromDb() {
        //加所以后,我们还要去缓存中确定一次,如果缓存中没有,才继续查数据库
        String catalogJson = redisTemplate.opsForValue().get("catalogJson");
        if (!StringUtils.isEmpty(catalogJson)) {
            //如果缓存中有,从缓存中获取
            Map<String, List<Catalog2Vo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {
            });
            return result;
        }
        System.out.println("查询了数据库~");

        List<CategoryEntity> selectList = baseMapper.selectList(null);

        //1.查出所有1级分类
        List<CategoryEntity> leve1Categorys = getParent_cid(selectList, 0L);

        //2.封装数据
        Map<String, List<Catalog2Vo>> parentCid = leve1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
            //1.每一个一级分类
            List<CategoryEntity> categoryEntities = getParent_cid(selectList, v.getCatId());
            List<Catalog2Vo> catalog2Vos = null;
            if (categoryEntities != null) {
                catalog2Vos = categoryEntities.stream().map(l2 -> {
                    Catalog2Vo vo = new Catalog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());
                    //找二级分类的三级分类
                    List<CategoryEntity> categoryEntities3 = getParent_cid(selectList, l2.getCatId());
                    if (categoryEntities3 != null) {
                        List<Catalog2Vo.Catalog3Vo> collect = categoryEntities3.stream().map(l3 -> {
                            Catalog2Vo.Catalog3Vo catalog3Vo = new Catalog2Vo.Catalog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());
                            return catalog3Vo;
                        }).collect(Collectors.toList());
                        vo.setCatalog3List(collect);
                    }
                    return vo;
                }).collect(Collectors.toList());
            }
            return catalog2Vos;
        }));
        //4.将从数据库中获取的数据,转换为Json存储到redis
        String s = JSON.toJSONString(parentCid);
        //设置缓存时间,方式雪崩
        redisTemplate.opsForValue().set("catalogJson", s, 1, TimeUnit.DAYS);
        return parentCid;
    }

🚗🚗🚗7.整合redisson作为分布式锁

7.1引入依赖

       <!--redisson作为所有分布式锁-->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.12.0</version>
        </dependency>

7.2程序化配置

在配置地址时,一定要添加reds://

@Configuration
public class MyRedissonConfig {


    @Bean(destroyMethod = "shutdown")
    public RedissonClient redissonClient() throws IOException {
        //1.创建配置
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.20.130:6379");
        //2.根据config创建redisson实例
        RedissonClient redissonClient = Redisson.create(config);
        return redissonClient;
    }
}

7.3实例解析

  • 1.锁的自动续期,如果业务超长,运行期间自动给锁续上新的30秒,不用担心业务超长,所自动过期被删除掉
  • 2.加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认在30秒后自动删除
 @Autowired
 RedissonClient redissonClient;

 @ResponseBody
    @GetMapping("/hello")
    public String hello() {
        //1.设置redis的key获取一把锁,只要名字相同,就是同一把锁
        RLock lock = redissonClient.getLock("my-lock");
        //2.手动枷锁
        lock.lock();//阻塞式等待,默认30秒
        try {
            //3.执行业务
            System.out.println("枷锁成功!" + Thread.currentThread().getName());
            //4.模拟业务消耗时间
            Thread.sleep(20000);
        } catch (Exception e) {

        } finally {
            //3.释放锁
            System.out.println("释放锁~"+Thread.currentThread().getName());
            lock.unlock();//不删除,默认30秒后过期,自动删除
        }
        return "hello";
    }
  • 3.如果我们传递了锁的超时时间,就发送给redis执行脚本,进行占锁,默认超时就是我们指定的时间
  • 4.如果未指定锁的超时时间,就使用30*1000【LockWatchingTimeOut看门狗默认时间】 
    • 只要占锁成功,就会启动一个定时任务【重新给锁设置过期时间,新的过期时间就是看门狗的默认时间】,每隔(【LockWatchingTimeOut看门狗默认时间】/3)折磨长时间自动续期;

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

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

相关文章

uniapp封装文字提示气泡框toolTip组件

uniapp封装文字提示气泡框toolTip组件 文字提示气泡框&#xff1a;toolTip 因为uniapp 中小程序中没有window对象&#xff0c;需手动调用 关闭 第一种办法关闭&#xff1a;this.$refs.tooltip.close() 第二种办法关闭&#xff1a;visible.sync false 移动端没有现成的toolTip组…

pytorch项目代码记录

目录 1.超过二维的张量写进csv 2.翻转矩阵与绘制热力图 3.切片 4.np按块复制 1.超过二维的张量写进csv #(20,204,273) -> (4080,273) ycsv []for i in range(20):ycsv.append(y[i, 8, :, :].reshape(204,273))with open(y.csv,w,encodingutf-8) as y_obj:writer csv.…

SpringCloud 服务的注册与发现

一、前言 接下来是开展一系列的 SpringCloud 的学习之旅&#xff0c;从传统的模块之间调用&#xff0c;一步步的升级为 SpringCloud 模块之间的调用&#xff0c;此篇文章为第二篇&#xff0c;即使用服务注册和发现的组件&#xff0c;此篇文章会介绍 Eureka、Zookeeper 和 Consu…

C++初阶 类(上)

目录 1. 什么是类 2. 如何定义出一个类 3. 类的访问限定符 4. 类的作用域 5. 类的实例化 6. 类的大小 7. this指针 1.this指针的引出 2. this指针的特性 8. 面试题 1. 什么是类 在C语言中&#xff0c;不同类型的数据集合体是结构体。为了方便管理结构体&#xff0c;我…

CVHub | 万字长文带你全面解读视觉大模型(建议收藏!)

本文来源公众号“CVHub”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;万字长文带你全面解读视觉大模型 0 导读 众所周知&#xff0c;视觉系统对于理解和推理视觉场景的组成特性至关重要。这个领域的挑战在于对象之间的复杂关系…

centos7 python3.12.1 报错 No module named _ssl

https://blog.csdn.net/Amio_/article/details/126716818 安装python cd /usr/local/src wget https://www.python.org/ftp/python/3.12.1/Python-3.12.1.tgz tar -zxvf Python-3.12.1.tgz cd Python-3.12.1/ ./configure -C --enable-shared --with-openssl/usr/local/opens…

Docker镜像及Dockerfile详解

1 Docker镜像用途 统一应用发布的标准格式支撑一个Docker容器的运行 2 Docker镜像的创建方法 基于已有镜像创建基于本地模板创建基于Dockerfile创建 &#xff08;实际环境中用的最多&#xff09; 2.1 基于已有镜像的创建 将容器里面运行的程序及运行环境打包生成新的镜像 …

网络工程师笔记10 ( RIP / OSPF协议 )

RIP 学习路由信息的时候需要配认证 RIP规定超过15跳认定网络不可达 链路状态路由协议-OSPF 1. 产生lsa 2. 生成LSDB数据库 3. 进行spf算法&#xff0c;生成最有最短路径 4. 得出路由表

IOS开发0基础入门UIkit-1cocoapod安装、更新和使用 , 安装中出现的错误及解决方案 M1或者M2安装cocoapods

cocoapod是ios开发时常用的包管理工具 1.M1或者是M2系统安装cocoapods先操作一下两个设置 1、打开访达->应用->实用工具->终端->右键点击终端->显示简介->勾选使用 Rosetta 打开&#xff0c;关闭终端&#xff0c;重新打开。 2、打开访达->应用->Xcod…

如何制作一个分销商城小程序_揭秘分销商城小程序的制作秘籍

打造赚钱神器&#xff01;揭秘分销商城小程序的制作秘籍 在这个数字化高速发展的时代&#xff0c;拥有一个属于自己的分销商城小程序&#xff0c;已成为众多商家和创业者的必备利器。它不仅能够快速搭建起自己的在线销售渠道&#xff0c;还能够利用分销模式&#xff0c;迅速裂…

处理error: remote origin already exists.及其Gitee文件上传保姆级教程

解决error: remote origin already exists.&#xff1a; 删除远程 Git 仓库 git remote rm origin 再添加远程 Git 仓库 git remote add origin &#xff08;HTTPS&#xff09; 比如这样&#xff1a; 然后再push过去就ok了 好多人可能还是不熟悉怎么将文件上传 Gitee:我…

spm用于颅骨去除和配准

1. 颅骨去除 出现这个界面就一直等待即可&#xff1a; segment的结果文件中会出现四个文件夹label、mri、report、surf 在mri文件中&#xff0c;mwp1是分割出来的灰质图像&#xff0c;mwp2是分割出来的白质图像&#xff0c;这两图像均是bias correction和空间配准后的。p0**…

log4j2 远程代码执行漏洞复现(CVE-2021-44228)

写在前面 log4j 对应的是 CVE-2017-5645&#xff0c;即 Apache Log4j Server 反序列化命令执行漏洞&#xff1b; log4j2 对应的是 CVE-2021-44228&#xff0c;即 log4j2 远程代码执行漏洞&#xff0c;通过 JNDI 注入实现&#xff1b; CVE-2017-5645 比较久远因此我们这里不做…

mysql的语法学习总结3(一些常见的问题)

执行后&#xff0c;MySQL 会重新加载授权表并更新权限。 FLUSH PRIVILEGES; 怎么检查自己的电脑端口3306有没有被占用&#xff1f; ESTABLISHED表示被占用&#xff0c;LISTENING表示端口正在被监听&#xff0c;22696是占用该端口的进程的PID&#xff08;进程标识符&#xff0…

羊大师揭秘,女性喝羊奶有什么好处

羊大师揭秘&#xff0c;女性喝羊奶有什么好处 女性喝羊奶有多种好处。首先&#xff0c;羊奶富含钙元素&#xff0c;有助于预防女性体内缺钙和老年女性骨质疏松&#xff0c;从而增强骨骼密度。其次&#xff0c;羊奶中的色氨酸和烟酸等成分有助于促进睡眠&#xff0c;改善睡眠质…

BUUCTF-MISC-[HDCTF2019]信号分析1

题目链接&#xff1a;BUUCTF在线评测 (buuoj.cn) 下载附件是一个WAV的文件&#xff0c;题目又叫做信号分析&#xff0c;用Adobe Audition 打开分析了 发现有很多长短不一样的信号&#xff0c;只需要分析一段 猜测长的是一短的为0 最后得到0101010101010101000000110 百度得知…

TMS智慧园区物流车辆管理系统:精确监控与高效运营的完美结合

在当今高速发展的物流行业中&#xff0c;效率和精确性是成功的关键。随着科技的不断进步&#xff0c;智慧园区物流车辆管理系统应运而生&#xff0c;为物流行业带来了革命性的变革。这些系统不仅提高了物流运作的效率&#xff0c;还为企业提供了更精确的监控和管理手段。接下来…

【OpenGL实现 03】纹理贴图原理和实现

目录 一、说明二、纹理贴图原理2.1 纹理融合原理2.2 UV坐标原理 三、生成纹理对象3.1 需要在VAO上绑定纹理坐标3.2 纹理传递3.3 纹理buffer生成 四、代码实现&#xff1a;五、着色器4.1 片段4.2 顶点 五、后记 一、说明 本篇叙述在画出图元的时候&#xff0c;如何贴图纹理图片…

NLP_文本张量表示方法_2(代码示例)

目标 了解什么是文本张量表示及其作用.文本张量表示的几种方法及其实现. 1 文本张量表示 将一段文本使用张量进行表示&#xff0c;其中一般将词汇为表示成向量&#xff0c;称作词向量&#xff0c;再由各个词向量按顺序组成矩阵形成文本表示. ["人生", "该&q…

【深度学习笔记】稠密连接网络(DenseNet)

注&#xff1a;本文为《动手学深度学习》开源内容&#xff0c;部分标注了个人理解&#xff0c;仅为个人学习记录&#xff0c;无抄袭搬运意图 5.12 稠密连接网络&#xff08;DenseNet&#xff09; ResNet中的跨层连接设计引申出了数个后续工作。本节我们介绍其中的一个&#xf…