谷粒商城【成神路】-【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信息
springredis:port: 6379host: ip地址password: XXX

3.2优化查询 

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

 /*** 使用redis缓存*/@Overridepublic Map<String, List<Catalog2Vo>> getCatalogJson() {//1.加入缓存,缓存中存的数据全都是jsonString 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缓存*/@Overridepublic 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存储到redisString s = JSON.toJSONString(parentCid);//设置缓存时间,方式雪崩redisTemplate.opsForValue().set("catalogJson", s, 1, TimeUnit.DAYS);return parentCid;}}

🚗🚗🚗6.添加分布式锁

使用分布式锁 步骤

  • 1.先去redis中设置一个key,为了保持原子性,同时设置过期时间
  • 2.判断是否设置成功,成功则继续业务操作,失败则自选再次获取
  • 3.执行业务之后,需要删除key释放锁,为了保持原子性,使用lua脚本
 /*** 使用redis缓存*/@Overridepublic 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存储到redisString 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秒后自动删除
 @AutowiredRedissonClient 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)折磨长时间自动续期;

7.4读写锁 

  • 1.保证一定能读到最新数据,修改期间,写锁是一个排他锁,读锁是一个共享锁
  • 2.读+读:相当于并发,在redis中记录好,都会读取成功
  • 3.写+读:等待写锁释放
  • 4.写+写:阻塞方式
  • 5.读+写:有读锁,写也需要等待
 @GetMapping("/write")@ResponseBodypublic String write() {RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("readWrite-lock");String word = "";RLock rLock = readWriteLock.writeLock();try {//该数据加写锁,读数据加读锁rLock.lock();word = UUID.randomUUID().toString();Thread.sleep(3000);redisTemplate.opsForValue().set("writeValue", word);} catch (InterruptedException e) {e.printStackTrace();} finally {rLock.unlock();}return word;}/*** 读写锁* @return*/@GetMapping("/read")@ResponseBodypublic String read() {String word = "";RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("readWrite-lock");//加读锁RLock rLock = readWriteLock.readLock();rLock.lock();try {word = redisTemplate.opsForValue().get("writeValue");} catch (Exception e) {e.printStackTrace();}finally {rLock.unlock();}return word;}

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

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

相关文章

php调用guzzlehttp库时出现Segmentation fault的解决方案

先说结论&#xff0c;这个问题的原因是因为php7.4与openssl3不兼容产生的&#xff0c;解决方案如下&#xff1a; 输入openssl version -a查看openssl版本&#xff0c;如果是3以上的版本与php7.4不兼容&#xff0c;7.4以下的没测试过&#xff0c;估计也有问题。我最终是安装上了…

深入理解Vue.js中的nextTick:实现异步更新的奥秘

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

微信小程序开发系列(二十)·wxml语法·setData()修改对象类型数据、ES6 提供的展开运算符、delete和rest的用法

目录 1. 新增单个、多个属性 1.1 新增单个属性 1.2 新增多个属性 2. 修改单个、多个属性 2.1 修改单个属性 2.2 修改多个属性 3. 优化 3.1 ES6 提供的展开运算符 3.2 Object.assign()将多个对象合并为一个对象 4. 删除单个、多个属性 4.1 删除单个属性 …

【Redis】RedisTemplate序列化传输数据

使用自定义的序列化器 使用RedisTemplate默认的序列化器发送数据&#xff0c;会将key全都当成Object处理&#xff0c;从而按照对象的方式转成json格式发送到服务器&#xff0c;这样会导致两个问题。一是不方便阅读&#xff0c;二是会大大浪费内存。因此&#xff0c;建议自定义…

MySQL常见的索引类型介绍

我将为您详细讲解 MySQL 中常见的索引类型&#xff0c;以及它们的使用场景、特点、区别和优势。索引是提高数据库查询性能的关键工具&#xff0c;它可以加速数据检索速度&#xff0c;减少服务器的负担。在 MySQL 中&#xff0c;索引类型主要包括 B-Tree 索引、哈希索引、全文索…

分库分表浅析原理

数据库存放数据大了&#xff0c;查询等操作就会存在瓶颈&#xff0c;怎么办&#xff1f; 1. 如果是单张表数据大了&#xff0c;可以在原有库上新建几张表table_0、table_1、table_2、.....table_n 写程序对数据进行分表&#xff1a; --这里提供一种一种分表策略,这里只需维护…

【C++】设计模式:观察者、策略、模板

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍设计模式&#xff1a;观察者、策略、模板。 学其所用&#xff0c;用其所学。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&#xf…

Python办公自动化之PDF(二)

Python操作PDF二 1、PyMuPDF简介2、 1、PyMuPDF简介 PyMuPDF&#xff08;也称Fitz&#xff09;开源&#xff0c;提供了一整套用于处理PDF文件的综合工具。使用PyMuPDF&#xff0c;用户可以高效地执行打开PDF、提取文本、图像和表格、操作旋转和裁剪等页面属性、创建新PDF文档以…

微服务day06-Docker

Docker 大型项目组件较多&#xff0c;运行环境也较为复杂&#xff0c;部署时会碰到一些问题&#xff1a; 依赖关系复杂&#xff0c;容易出现兼容性问题 开发、测试、生产环境有差异 1.什么是Docker? 大型项目组件很多&#xff0c;运行环境复杂&#xff0c;部署时会遇到各种…

sql注入基础学习

1.常用SQL语句 01、显示数据库 show databases&#xff1b; 02、打开数据库 use db name&#xff1b; 03、显示数据表 show tables&#xff1b; 04、显示表结构 describe table_name&#xff1b; 05、显示表中各字段信息&#xff0c;即表结构 show columns from table_nam…

大数据开发 hadoop集群 2.hadoop框架入门

自从我学会了寻找&#xff0c;我就已经找到 ——史铁生 —— 24.3.10 内容简介 Hadoop入门&#xff1a; ①概念 ②环境准备 ③hadoop生产集群搭建 ④常见错误的解决方案 ①概念&#xff1a;1.Hadoop是什么 2.Hadoop发展历史 3.Hadoop…

菜品检测,基于YOLOV8

菜品检测&#xff0c;基于YOLOV8NANO&#xff0c;训练得到模型PT&#xff0c;然后转换成ONNX&#xff0c;OPENCV的DNN调用&#xff0c;支持C/PYTHON/ANDROID开发菜品检测&#xff0c;基于YOLOV8&#xff0c;能检测五种菜品&#xff0c;水豆腐、豆腐干、空心菜、豆芽菜、茄子

详情接口API解密:淘宝天猫帮你买到最适合的商品!

在当前的电商时代&#xff0c;淘宝天猫已成为许多人购物的首选平台。然而&#xff0c;随着商品种类的繁多和信息的不对称&#xff0c;消费者在面对众多的商品时往往感到犹豫和困惑。这时&#xff0c;淘宝天猫的详情接口API就像是一把大数据的黄金钥匙&#xff0c;联讯数据为购物…

python并发编程:异步IO(Asynchronous I/O)

异步IO(Asynchronous I/O) Linux下的asynchronous IO其实用得不多&#xff0c;从内核2.6版本才开始引入。先看一下它的流程&#xff1a; 用户进程发起read操作之后&#xff0c;立刻就可以开始去做其它的事。而另一方面&#xff0c;从kernel的角度&#xff0c;当它受到一个asyn…

基于状态机的按键消抖实现

摸鱼记录 Day_14 !(^O^)y review 在day_13中以按键状态判断为例学习了状态分析基于状态机的按键消抖原理-CSDN博客 分析得到了下图&#xff1a; 今日任务&#xff1a;完成此过程 !(^O^)y 小梅哥对应视频&#xff1a; 15B 基于状态机的按键消抖Verilog实现_哔哩哔哩…

Linux进程概念僵尸进程孤儿进程

文章目录 一、什么是进程二、进程的状态三、Linux是如何做的&#xff1f;3.1 R状态3.2 S状态3.3 D状态3.4 T状态3.5 t状态3.6 X状态3.7 Z状态 四、僵尸进程4.1 僵尸进程危害 五、孤儿进程 一、什么是进程 对于进程理解来说&#xff0c;在Windows上是也可以观察到的&#xff0c…

ManualResetEvent 在线程中的使用C#

ManualResetEvent 用于表示线程同步事件&#xff0c;可以使得线程等待信号发射之后才继续执行下一步&#xff0c;否则一直处于等待状态中。 ManualResetEvent 的常用方法 构造函数ManualResetEvent(bool); ManualResetEvent manualResetEvent new ManualResetEvent(false…

16. 获取社区详情

文章目录 一、建表、插入测试数据、并生成对应的model二、添加路由&#xff0c;以及controller、logic、dao三层分别实现对应代码三、编译运行 登录之后&#xff0c;我们可以发表帖子&#xff0c;但是发表帖子之前&#xff0c;需要先选择一个频道&#xff0c;可以理解是社区分类…

什么是高级编程语言?——跟老吕学Python编程

什么是高级编程语言&#xff1f;——跟老吕学Python编程 高级编程语言简介高级编程语言发展历程高级编程语言特点高级编程语言分类命令式语言函数式语言逻辑式语言面向对象语言 常见的高级编程语言及其特点和应用领域高级编程语言性能分析高级编程语言的工作方式 高级编程语言简…

MUMU模拟器12连logcat的方法

大家好&#xff0c;我是阿赵。   在开发手机游戏的时候&#xff0c;在真机上会出现各种问题&#xff0c;在查询问题的时候&#xff0c;安卓手机需要用adb连接来连接手机看logcat输出分析问题。但由于连接手机比较麻烦&#xff0c;所以我都习惯在电脑用安卓模拟器来测试。   …