gulimall-缓存-缓存使用

文章目录

  • 前言
  • 一、本地缓存与分布式缓存
    • 1.1 使用缓存
    • 1.2 本地缓存
    • 1.3 本地模式在分布式下的问题
    • 1.4 分布式缓存
  • 二、整合redis测试
    • 2.1 引入依赖
    • 2.2 配置信息
    • 2.3 测试
  • 三、改造三级分类业务
    • 3.1 代码改造
  • 四、高并发下缓存失效问题
    • 4.1 缓存穿透
    • 4.2 缓存雪崩
    • 4.3 缓存击穿
  • 五、分布式下加锁
    • 5.1 分布式锁示意图
    • 5.2 锁的时序问题

前言

本文继续记录B站谷粒商城项目视频 P151-157 的内容,做到知识点的梳理和总结的作用。

一、本地缓存与分布式缓存

1.1 使用缓存

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

  1. 即时性、数据一致性要求不高的
  2. 访问量大且更新频率不高的数据(读多,写少)
    举例:电商类应用,商品分类,商品列表等适合缓存并加一个失效时间(根据数据更新频率来定),后台如果发布一个商品,买家需要 5 分钟才能看到新的商品一般还是可以接受的。

在这里插入图片描述
伪代码

data = redisTemplate.opsForValue().get(redisKey);//从缓存加载数据
If(data == null){//缓存中没有则从数据库加载数据data = db.getDataFromDB(id);//保存到 cache 中redisTemplate.opsForValue().set(redisKey,data);
}
return data;

1.2 本地缓存

在单体项目中,我们可以使用 Map 集合存储数据作为项目的本地缓存,因为 Map 数据是存储与内存的,相比于数据库查询要从磁盘加载到内存有着更高的效率。

在这里插入图片描述

1.3 本地模式在分布式下的问题

但是在分布式情况下这种情况就不再适用了,每个微服务可能部署在多台机器上,每个机器上有各自的缓存 Map 对象,会导致数据不一致的问题。
在这里插入图片描述

1.4 分布式缓存

所以应该将数据缓存在同一个缓存中间件中,才能保证数据一致性问题

在这里插入图片描述

二、整合redis测试

2.1 引入依赖

<!-- 缓存中间件redis依赖-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2.2 配置信息

spring:redis:host: 192.168.57.129port: 6379

2.3 测试

@Autowired
StringRedisTemplate redisTemplate;@Test
public void testRedis() {//存储redisTemplate.opsForValue().set("HELLO_REDIS", "SpringBoot!");//获取String value = redisTemplate.opsForValue().get("HELLO_REDIS");System.out.println(value);
}

在这里插入图片描述

三、改造三级分类业务

3.1 代码改造

@Override
public Map<String, List<Catelog2Vo>> getCatalogJson() {//给缓存中放json字符串,拿出的json字符串,还用逆转为能用的对象类型:【序列化与反序列化】/*** 1、空结果缓存:解决缓存穿透* 2、设置过期时间(加随机值):解决缓存雪崩* 3、加锁:解决缓存击穿*///1、加入缓存逻辑,缓存中存的数据是json字符串。//JSON跨语言,跨平台兼容。String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");if (StringUtils.isEmpty(catalogJSON)) {//2、缓存中没有,查询数据库//保证数据库查询完成以后,将数据放在redis中,这是一个原子操作。log.info("缓存不命中....将要查询数据库...");Map<String, List<Catelog2Vo>> catalogJsonFromDb = getCatalogJsonFromDB();String result = JSON.toJSONString(catalogJsonFromDb);redisTemplate.opsForValue().set("catalogJSON",result);}log.info("缓存命中....直接返回....");//转为我们指定的对象。return JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>() {});
}

四、高并发下缓存失效问题

4.1 缓存穿透

在这里插入图片描述
解决方案1:null 结果放入缓存,并加入短暂的过期时间

伪代码

//从缓存加载数据
data = redisTemplate.opsForValue().get(redisKey);
If(data == null){//缓存中没有则从数据库加载数据data = db.getDataFromDB(id);if(data == null) {//空结果保存到 cache 中redisTemplate.opsForValue().set(redisKey,null,300,TimeUnit.SECONDS);}else {//保存到 cache 中redisTemplate.opsForValue().set(redisKey,data);}
}
return data;

解决方案2:使用布隆过滤器
这种技术在缓存之前再加一层屏障,里面存储目前数据库中存在的所有key。当业务系统有查询请求的时候,首先去BloomFilter中查询该key是否存在。若不存在,则说明数据库中也不存在该数据,因此缓存都不要查了,直接返回null。若存在,则继续执行后续的流程,先前往缓存中查询,缓存中没有的话再前往数据库中的查询。伪代码如下:

String get(String key) {String value = redis.get(key);     if (value  == null) {if(!bloomfilter.mightContain(key)){//不存在则返回return null; }else{//可能存在则查数据库value = db.get(key); redis.set(key, value); }    }return value;
}

布隆过滤器示意图

在这里插入图片描述

4.2 缓存雪崩

在这里插入图片描述

4.3 缓存击穿

在这里插入图片描述

五、分布式下加锁

5.1 分布式锁示意图

本地锁,只能锁住当前进程,所以我们需要分布式锁。
在这里插入图片描述

5.2 锁的时序问题

在这里插入图片描述

@Override
public Map<String, List<Catelog2Vo>> getCatalogJson() {//给缓存中放json字符串,拿出的json字符串,还用逆转为能用的对象类型:【序列化与反序列化】/*** 1、空结果缓存:解决缓存穿透* 2、设置过期时间(加随机值):解决缓存雪崩* 3、加锁:解决缓存击穿*///1、加入缓存逻辑,缓存中存的数据是json字符串。//JSON跨语言,跨平台兼容。String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");if (StringUtils.isEmpty(catalogJSON)) {//2、缓存中没有,查询数据库//保证数据库查询完成以后,将数据放在redis中,这是一个原子操作。log.info("缓存不命中....将要查询数据库...");Map<String, List<Catelog2Vo>> catalogJsonFromDb = getCatalogJsonFromDB();return catalogJsonFromDb;}log.info("缓存命中....直接返回....");//转为我们指定的对象。return JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>() {});
}

查询数据库后将结果放入缓存,保证这是一个原子性操作,防止多个线程查询数据库而导致日志输出多个查询了数据库…

//从数据库查询并封装分类数据
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDB() {//只要同一把锁,就能锁住需要这个锁的所有线程//synchronized (this):springBoot所有组件在容器中都是单实例的//TODO 本地锁:synchronized JUC(Lock) 在分布式情况下只能使用分布式锁才能锁住资源synchronized (this) {//得到锁以后,我们应该再去缓存中确定一次,如果没有才需要继续查询String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");if (!StringUtils.isEmpty(catalogJSON)) {return JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>() {});}log.info("查询了数据库....");//1、将数据库的多次查询变为一次,查询所有分类信息List<CategoryEntity> selectList = baseMapper.selectList(null);//1、查出所有1级分类List<CategoryEntity> level1Categorys = getParent_cid(selectList, 0L);//2、封装数据Map<String, List<Catelog2Vo>> parent_cid = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {//1、每一个的一级分类,查到这个一级分类的二级分类List<CategoryEntity> categoryEntities = getParent_cid(selectList, v.getCatId());//2、封装上面的结果List<Catelog2Vo> catelog2Vos = null;if (categoryEntities != null) {catelog2Vos = categoryEntities.stream().map(l2 -> {Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());//1、找当前二级分类的三级分类封装成voList<CategoryEntity> level3Catelog = getParent_cid(selectList, l2.getCatId());if (level3Catelog != null) {List<Catelog2Vo.Catelog3Vo> collect = level3Catelog.stream().map(l3 -> {//2、封装成指定格式Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());return catelog3Vo;}).collect(Collectors.toList());catelog2Vo.setCatalog3List(collect);}return catelog2Vo;}).collect(Collectors.toList());}return catelog2Vos;}));String result = JSON.toJSONString(parent_cid);redisTemplate.opsForValue().set("catalogJSON",result,1,TimeUnit.DAYS);return parent_cid;}
}

压力测试结果:日志只输出一个查询了数据库…,表面只有一个线程查询了数据库。
在这里插入图片描述

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

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

相关文章

C++学习第十二天----指针

1.自动存储&#xff0c;静态存储和动态存储 c有3种管理数据内存的方式&#xff1a;自动存储&#xff0c;静态存储和动态存储&#xff08;有时也叫自由存储空间或堆&#xff09;&#xff1b;C其实还有第4种类型----线程存储&#xff1b; 第一&#xff0c;自动存储&#xff0c;在…

Talk | ICCV‘23 HumanMAC:简洁易拓展的人体动作预测新框架

​ 本期为TechBeat人工智能社区第522期线上Talk&#xff01; 北京时间8月16日(周三)20:00&#xff0c;清华大学博士生—陈凌灏的Talk已准时在TechBeat人工智能社区开播&#xff01; 他与大家分享的主题是: “HumanMAC-简洁易拓展的人体动作预测新框架”&#xff0c;介绍了人体动…

linux 学习————LNMP之分布式部署

目录 一、概述 二、LNMP环境部署 三、配置nginx 四、 配置php使nginx能够解析.php 五、配置mysql 六、配置discuz进行登录论坛访问测试 一、概述 LNMP代表 Linux、Nginx、MySQL、PHP&#xff0c;是一种常用的服务器架构。它由以下组件组成&#xff1a; Linux&#xff1a;作…

【js】js中apply()、bind()、call()用法

这三个方法的作用基本上相同&#xff0c;用法上有一些不同&#xff0c;下面先对比一下它们的用法&#xff1a; apply&#xff1a;调用一个具有给定 this 值的函数&#xff0c;以及以一个数组&#xff08;或一个类数组对象&#xff09;的形式提供的参数。 语法&#xff1a; ap…

Metasploitable2靶机漏洞复现

一、信息收集 nmap扫描靶机信息 二、弱口令 1.系统弱口令 在Kali Linux中使用telnet远程连接靶机 输入账号密码msfadmin即可登录 2.MySQL弱口令 使用mysql -h 靶机IP地址即可连接 3.PostgreSQL弱密码登录 输入psql -h 192.168.110.134 -U postgres 密码为postgres 输入\…

每天一个电商API分享:淘宝商品销量接口,获取淘宝商品销售数据(淘宝商品精准销量、总销量、月销量)

开发背景 商品销量是商家决策的重要参考指标之一。通过淘宝商品销量接口&#xff0c;商家可以获取到实时的销量信息&#xff0c;从而更好地进行库存管理、供应链计划和市场推广等决策。 淘宝商品销量接口可以提供丰富的销售数据&#xff0c;包括销售数量、销售额、销售渠道等…

unity中导入自定义模型

unity中导入自定义模型 准备软件步骤1从SoildWorks中导出模型为STEP格式2将STEP格式文件导入到3DS Max中&#xff0c;再导出为FBX格式3将FBX格式导入至unity中 准备软件 需要SoildWorks、3DS Max和Unity 3D软件步骤 1从SoildWorks中导出模型为STEP格式 2将STEP格式文件导入到…

【数据结构】 List与顺序表及接口的实现

文章目录 什么是List常见接口介绍线性表顺序表顺序表接口的实现add在末尾新增元素在 pos 位置新增元素判定是否包含某个元素查找某个元素对应的位置获取 pos 位置的元素给 pos 位置的元素设为 value删除第一次出现的关键字key获取顺序表的长度清空顺序表 顺序表的优缺点优点&am…

全面掌握 Jaeger 分布式调用链路跟踪理论和实战,Go 为所有使用 go-resty 库发起 HTTP 请求集成链路跟踪 jaeger(附源码)

全面掌握 Jaeger 分布式调用链路跟踪理论和实战,Go 为所有使用 go-resty 库发起 HTTP 请求集成链路跟踪 jaeger(附源码)。 介绍一个开源的分布式跟踪系统 Jaeger,首先从理论基础知识开始学习,将学习如何在 HTTP 请求中集成链路跟踪,以及如何在 GORM 框架实现,最后学习 …

Qt应用开发(基础篇)——滚屏区域基类 QAbstractScrollArea

一、前言 QAbstractScrollArea滚屏区域抽象类继承于QFrame&#xff0c;QFrame继承于QWidget&#xff0c;是QListview(列表浏览器)、QTableview(表格浏览器)、QTextEdit(文本编辑器)、QTextBrowser(文本浏览器)等所有需要滚屏区域部件的抽象基类。 框架类QFrame介绍 QAbstractSc…

java -jar 启动服务后,关闭命令窗口后服务停止

java -jar 启动服务后&#xff0c;关闭命令窗口后服务停止 问题&#xff1a;当我们用java -jar命令启动服务后&#xff0c;只有一直保持Xshell的窗口开启且正常连接服务器时才能访问服务&#xff0c;当关闭命令窗口时&#xff0c;服务会停止运行 解决&#xff1a;使用nohup命…

java之juc二

JMM 请你谈谈对Volatile的理解 Volatile是jvm提供的轻量级的同步机制&#xff08;和synchronized差不多&#xff0c;但是没有synchronized那么强大&#xff09; 保证可见性不保证原子性禁止指令重排 什么是JMM JMM&#xff1a;java内存模型&#xff0c;不存在的东西&#…

常用Linux命令

常用Linux命令 1. 基本命令 uname -m 显示机器的处理器架构 uname -r 显示正在使用的内核版本 dmidecode -q 显示硬件系统部件 (SMBIOS / DMI) hdparm -i /dev/hda 罗列一个磁盘的架构特性 hdparm -tT /dev/sda 在磁盘上执行测试性读取操作系统信息 arch 显示机器的处理器架构…

一.RocketMQ概念

RocketMQ概念 1.概念2.应用场景3.MQ的优点和缺点4.常见MQ对比 1.概念 MQ(Message Queue)&#xff0c;是一种提供消息队列服务的中间件&#xff0c;也称为消息中间件&#xff0c;是一套提供了消息生产、存储、消费全过程API的软件系统。 RocketMQ是阿里巴巴2016年MQ中间件&…

Error: Couldn‘t find preset “es2015“ relative to directory

vue引入element-ui&#xff0c;运行时报了这个错误 Module build failed: Error: Couldnt find preset "es2015" relative to directory "D:\\360MoveData\\Users\\Administrator\\Desktop\\新建文件夹\\henge-test"at D:\360MoveData\Users\Administrato…

华为云classroom赋能--Devstar使应用开发无需从零开始

华为云DevStar为开发者提供业界主流框架代码初始化能力&#xff0c;通过GUI、API、CLI等多种方式&#xff0c;将按模板生成框架代码的能力推送至用户桌面。同时基于华为云服务资源、成熟的DevOps开发工具链和面向多场景的众多开发模板&#xff0c;提供一站式创建代码仓、自动生…

Windows server 2016如何安装OpenSSH

在 Windows Server 2016 上安装 OpenSSH 需要通过“添加功能和角色”向导来完成。以下是安装 OpenSSH 的步骤&#xff1a; 1.打开 Windows Server 2016 控制面板。 2.点击 "程序"&#xff0c;然后选择 "程序和功能"。 3.在左侧菜单中&#xff0c;点击 &…

【golang】数组和切片底层原理

数组类型的值&#xff08;以下简称数组&#xff09;的长度是固定的&#xff0c;而切片类型的值&#xff08;以下简称切片&#xff09;是可变长的。 数组的长度在声明它的时候就必须给定&#xff0c;并且之后不会再改变。可以说&#xff0c;数组的长度是其类型的一部分。比如&a…

Spring学习笔记之Spring IoC注解式开发

文章目录 声明Bean的注解Component注解Controller注解Service注解Repository Spring注解的使用选择性实例化Bean负责注入的注解ValueAutowired与QuaifierResource 全注解式开发 注解的存在主要是为了简化XML的配置。Spring6倡导全注解开发 注解怎么定义&#xff0c;注解中的属性…

深入探索JavaEE单体架构、微服务架构与云原生架构

课程链接&#xff1a; 链接: https://pan.baidu.com/s/1xSI1ofwYXfqOchfwszCZnA?pwd4s99 提取码: 4s99 复制这段内容后打开百度网盘手机App&#xff0c;操作更方便哦 --来自百度网盘超级会员v4的分享 课程介绍&#xff1a; &#x1f50d;【00】模块零&#xff1a;开营直播&a…