一.知识回顾
【0.三高商城系统的专题专栏都帮你整理好了,请点击这里!】
【1-系统架构演进过程】
【2-微服务系统架构需求】
【3-高性能、高并发、高可用的三高商城系统项目介绍】
【4-Linux云服务器上安装Docker】
【5-Docker安装部署MySQL和Redis服务】
【6-Git安装与配置过程、Gitee码云上创建项目、IDEA关联克隆的项目】
【7-创建商城系统的子模块并将修改后的信息使用Git提交到Gitee上】
【8-数据库表结构的创建&后台管理系统的搭建】
【9-前端项目的搭建部署、Node安装、VSCode安装】
【10-Node的安装以及全局环境变量的相关配置&解决启动报错的问题(1.Error: Cannot find module ‘fs/promises)(2.npm安装node-sass报错)】
【11-导入人人generator项目并自动生成相关的文件&商品子模块的调试&公共模块common子模块的抽离与实现&Lombok插件的安装】
【12-商品子模块整合MyBatisPlus技术&其它模块通过generator的自动生成与补充完善】
【13-项目中微服务组件的学习-SpringCloudAlibaba微服务生态体系的学习&SpringCloudAlibaba的依赖管理&项目中SpringBoot和SpringCloud版本的统一】
【14-微服务的注册中心与配置中心Nacos&Windows操作系统上安装Nacos和Linux操作系统上用Docker中安装Nacos&每个子项目模块使用Nacos进行服务注册与发现】
【15-项目中服务的远程调用之OpenFeign&订单模块与商品模块集成使用OpenFeign的案例】
【16-配置中心之Nacos的基本使用&Nacos服务之命令空间、Nacos服务之配置组、Nacos服务之配置拆分】
【17-微服务网关之Spring Cloud Gateway&Spring Cloud Gateway网关服务搭建】
【18-业务开发-基础业务-商品模块-分类管理-前后端管理系统的启动-为分类管理表增加数据-Json插件的下载-返回具有层级目录、父子关系结构的数据】
【19-业务开发-基础业务-商品模块-分类管理-管理系统新建菜单-后端项目renren注册到Nacos注册中心和配置中心去-项目gateway网关模块的搭建-浏览器的同源策略与解决跨域问题实操案例】
【20-业务开发-基础业务-商品模块-分类管理-前端展示后端具有层级关系的目录数据-商品系统三级分类的逻辑删除前后端代码实现】
【21-业务开发-基础业务-商品模块-分类管理-商品系统三级分类的新增类别前后端代码实现-商品系统三级分类的更新类别前后端代码实现-之前错误的Bug修正】
【22-业务开发-基础业务-商品模块-分类管理-商品系统三级分类拖拽页面的功能-前后端代码的逻辑实现-访问测试-拖拽开关的开启和关系-批量更新拖拽数据-批量删除选定数据】
【23-业务开发-基础业务-品牌管理-品牌管理项目搭建-品牌管理实现的增删改查操作测试-后端数据显示状态使用前端组件开关按钮展示-以及数据处理以及测试】
【24-业务开发-基础业务-品牌管理-图片管理-阿里云OSS服务开通和使用-阿里云OSS服务API使用-SpringCloudAlibaba OSS服务的使用】
【25-业务开发-基础业务-品牌管理-图片管理-图片上传方式的三种实现方式-第三方公共服务模块集成到项目中-服务端生成签名实战】
【26-业务开发-基础业务-品牌管理-图片管理-上传图片功能实现-基于阿里云OSS服务-解决跨域问题-设置跨域规则-修改ACL权限为公共读】
【27-业务开发-基础业务-品牌管理-图片管理-添加修改品牌信息并显示图片-前端数据校验-后端数据JSR303校验实现-统一异常处理-自定义响应编码规则-分组校验-自定义校验注解-项目Bug解决】
【28-业务开发-基础业务-属性管理-SKU和SPU基本概念-SKU和SPU关联关系-属性实体之间的关联关系-批量菜单创建】
【29-业务开发-基础业务-属性管理-属性组业务逻辑开发-页面布局-三级分类组件功能-属性组表单-父子组件传值-属性组数据展示-属性组数据添加-属性组数据修改-前后端项目整合交互测试】
【30-业务开发-基础业务-品牌管理-分类维护-解决分类维护业务开发中的一个Bug-品牌管理-分页插件-分页功能的逻辑实现-品牌管理-检索条件模糊查询品牌管理-增加更新操作中排序字段检验还是存在问题】
【31-业务开发-基础业务-品牌管理-级联类别信息业务功能实现-品牌管理和商品分类管理俩者业务关联出现数据冗余,导致数据不同步的问题-开启事务-项目测试】
【32-业务开发-基础业务-规格参数-保存数据-查询数据-更新操作之数据回显展示-更新操作-前后端项目交互整合与测试-总结收获】
【33-业务开发-基础业务-规格参数-销售属性-多表之间的关联增删改查操作-前后端项目交互整合与测试-Cannot read property ‘publish‘ of undefined】
【34-业务开发-基础业务-属性组和基本属性-属性组和基本属性建立关联-属性组和基本属性解除关联-未关联属性查询-确认新增】
【35-业务开发-基础业务-商品服务-新增商品-会员模块服务-mall-member-会员模块数据维护-规格参数维护-前端项目Bug解决-PubSub依赖缺失】
【36-业务开发-基础业务-商品服务SPU-前后端处理商品数据Json-发布商品前后端业务逻辑-feign服务远程调用-DTO数据传输对象-商品服务的检索-商品管理的检索项目中修改更正完善逻辑操作】
【37-业务开发-基础业务-库存管理- 仓库模块Nacos注册中心的配置-Gateway网关配置-仓库维护的增删改查-商品库存管理-采购流程-采购需求维护-采购需求合并-领取采购单完成采购操作】
【插入------>ElasticSearch专栏相关的知识内容都整理好了,在这里哟!】
【38-商品上架功能结合ElasticSearch全文检索的流程-商品ES关系映射模型&Docker安装ik分词器-实现上架功能复杂的逻辑实现-Postman+Kibana访问测试】
【39-商品整合thymeleaf模板引擎-商城用户端的实现逻辑-部署devtools工具依赖-商品后台-三级分类逻辑分析实现-Docker 安装部署Nginx-Nginx对网关实现反向代理负载均衡】
【40-系统性能压力测试基本概念-相关性能指标HPS&TPS&QPS&RT-安装Jmeter教程-JMeter测试流程-线程组-取样器-监视器-测试商城首页-JMeter Address 占用的问题】
【41-系统性能压力测试优化-JVM知识回顾-jconsole和jvisualvm-jvisualvm安装Visual GC插件-Nginx压力测试- 网关gateway压测-Nginx实现动静分离】
二.缓存
2.1 缓存的基本概念?
缓存的作用是减低对数据源的访问频率。从而提高我们系统的性能。缓存的流程图:
2.2 没有使用缓存的场景
2.3 使用缓存的场景
三.缓存的分类
3.1 本地缓存
其实就是把缓存数据存储在内存中(Map <String,Object>
).在单体架构中肯定没有问题。
单体架构下的缓存处理
3.2 分布式缓存
在分布式环境下,我们原来的本地缓存就不是太使用了,原因是:
- 缓存数据冗余
- 缓存效率不高
分布式缓存的结构图
四.项目中整合Redis
商品模块中pom文件引入redis依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
application.yml配置文件中添加对应的配置信息
测试操作Redis的代码
@Autowired
StringRedisTemplate stringRedisTemplate;
@Test
public void testStringRedisTemplate(){
// 获取操作String类型的Options对象
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
// 插入数据
ops.set("testName","ljw"+ UUID.randomUUID());
// 获取存储的信息
System.out.println("Redis保存的值为:"+ops.get("testName"));
}
运行结果
查看可以通过Redis的客户端连接查看
通过RDM工具查看刚保存的值
五.修改三级分类逻辑代码+加入缓存
mall-commons项目中加入fastjson依赖
在首页查询二级和三级分类数据的时候我们可以通过Redis来缓存存储对应的数据,来提升检索的效率。
通过RDM工具查看缓存是否缓存成功
六.三级分类加入缓存后压力测试
压力测试内容 | 压力测试的线程数 | 吞吐量/s | 90%响应时间 | 99%响应时间 |
---|---|---|---|---|
Nginx | 100 | 5,184.43 | 33 | 96 |
Gateway | 100 | 20,176.133 | 8 | 14 |
单独测试服务 | 100 | 28,208.977 | 5 | 9 |
Gateway+服务 | 100 | 9,360.128 | 22 | 58 |
Nginx+Gateway+服务 | 100 | 2,437.736 | 49 | 61 |
一级菜单 | 100 | 199.876 | 532 | 542 |
三级菜单 | 100 | 10.562 | 14,556 | 21,744 |
一级菜单(DB-索引) | 100 | 62 | 26 | 35 |
三级分类压测(索引) | 100 | 4 | 10745 | 16424 |
首页全量数据(DB-Themleaf-放开缓存) | 100 | 15 | 崩盘 | 崩盘 |
三级分类压测(Redis缓存) | 100 | 13.2 | 13296 | 14622 |
jmeter设置请求参数
压测结果数据:
通过对比可以看到Redis缓存加入后的性能提升的效果还是非常明显的。
七.缓存穿透
指查询一个一定不存在的数据,由于缓存是不命中,将去查询数据库,但是数据库也无此记录,我们没有将这次查询的null写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义.
利用不存在的数据进行攻击,数据库瞬时压力增大,最终导致崩溃,解决方案也比较简单,直接把null结果缓存,并加入短暂的过期时间
八.缓存雪崩
缓存雪崩是指在我们设置缓存时key采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。
解决方案:原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
九.缓存击穿
对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。如果这个key在大量请求同时进来前正好失效,那么所有对这个key的数据查询都落到db,我们称为缓存击穿。
解决方案:加锁,大量并发只让一个去查,其他人等待,查到以后释放锁,其他人获取到锁,先查缓存,就会有数据,不用去db。
但是当我们压力测试的时候,输出的结果有点出乎我们的意料
做了两次的查询,原因是释放锁和查询结果缓存的时序问题
我们只需要调整下释放锁和结果缓存的时序问题就可以了
然后就是完整的代码处理
/**
* 查询出所有的二级和三级分类的数据
* 并封装为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;
}
}
好了,关于【42-缓存的基本概念-是否使用缓存的场景-本地缓存-分布式缓存-项目中整合Redis-修改三级分类逻辑代码+加入缓存-三级分类加入缓存后压力测试-缓存穿透-缓存雪崩-缓存击穿】就先学习到这里,更多的内容持续学习创作中。