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,一经查实,立即删除!

相关文章

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 输入\…

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…

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

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

java之juc二

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

一.RocketMQ概念

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

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

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

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

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

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

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

ARM-M0内核MCU,内置24bit ADC,采样率4KSPS,传感器、电子秤、体脂秤专用,国产IC

ARM-M0内核MCU 内置24bit ADC &#xff0c;采样率4KSPS flash 64KB&#xff0c;SRAM 32KB 适用于传感器&#xff0c;电子秤&#xff0c;体脂秤等等

[BitSail] Connector开发详解系列三:SourceReader

更多技术交流、求职机会&#xff0c;欢迎关注字节跳动数据平台微信公众号&#xff0c;回复【1】进入官方交流群 Source Connector 本文将主要介绍负责数据读取的组件SourceReader&#xff1a; SourceReader 每个SourceReader都在独立的线程中执行&#xff0c;只要我们保证Sou…

Jmeter进阶使用:BeanShell实现接口前置和后置操作

一、背景 我们使用Jmeter做压力测试或者接口测试时&#xff0c;除了最简单的直接对接口发起请求&#xff0c;很多时候需要对接口进行一些前置操作&#xff1a;比如提前生成测试数据&#xff0c;以及一些后置操作&#xff1a;比如提取接口响应内容中的某个字段的值。举个最常用…

c语言——拷贝数组

这段代码是一个简单的数组拷贝示例。它的功能是将一个原始数组 original 的内容拷贝到另一个数组 copied 中&#xff0c;并输出两个数组的元素。 代码执行过程如下&#xff1a; 首先&#xff0c;在 main() 函数中定义了一个整型数组 original&#xff0c;并初始化了它的元素。…

物联网在制造业中的应用

制造业目前正在经历第四次工业革命&#xff0c;物联网、人工智能和机器人等技术进步正在推动行业的发展。研究表明&#xff0c;到2024年&#xff0c;全球制造商将在物联网解决方案上投资700亿美元&#xff0c;许多制造商正在实施物联网设备&#xff0c;以利用预测性维护和复杂的…

接口测试工具——Postman测试工具 Swagger接口测试+SpringBoot整合 JMeter高并发测试工具

目录 Postman测试工具接口测试工具swaggerKnife4j1.引入依赖2.配置3.常用注解4.接口测试 JMeter什么是JMeter?JMeter安装配置1.官网下载2.下载后解压3.汉语设置 JMeter的使用方法1.新建线程组2.设置参数3.添加取样器4.设置参数&#xff1a;协议&#xff0c;ip&#xff0c;端口…

SDK是什么,SDK和API有什么区别

SDK&#xff08;Software Development Kit&#xff09;是一种开发工具包&#xff0c;通常由软件开发公司或平台提供&#xff0c;用于帮助开发人员构建、测试和集成特定平台或软件的应用程序。SDK 包含一系列的库、工具、示例代码和文档&#xff0c;旨在简化开发过程并提供所需的…

基于Mysql+Vue+Django的协同过滤和内容推荐算法的智能音乐推荐系统——深度学习算法应用(含全部工程源码)+数据集

目录 前言总体设计系统整体结构图系统流程图 运行环境Python 环境MySQL环境VUE环境 模块实现1. 数据请求和储存2. 数据处理计算歌曲、歌手、用户相似度计算用户推荐集 3. 数据存储与后台4. 数据展示 系统测试工程源代码下载其它资料下载 前言 本项目以丰富的网易云音乐数据为基…