【业务功能篇87】微服务-springcloud-本地缓存-redis-分布式缓存-缓存穿透-雪崩-击穿

一、缓存

1. 什么是缓存

  缓存的作用是减低对数据源的访问频率。从而提高我们系统的性能。

image.png

image.png

缓存的流程图

image.png

2.缓存的分类

2.1 本地缓存

  其实就是把缓存数据存储在内存中(Map <String,Object>).在单体架构中肯定没有问题。

image.png

单体架构下的缓存处理

image.png

2.2 分布式缓存

  在分布式环境下,我们原来的本地缓存就不是太使用了,原因是:

  • 缓存数据冗余
  • 缓存效率不高

image.png

  分布式缓存的结构图

image.png

3.整合Redis

  要整合Redis那么我们在SpringBoot项目中首页来添加对应的依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>

  然后我们需要添加对应的配置信息

image.png

测试操作Redis的数据

    @AutowiredStringRedisTemplate stringRedisTemplate;@Testpublic void testStringRedisTemplate(){// 获取操作String类型的Options对象ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();// 插入数据ops.set("name","bobo"+ UUID.randomUUID());// 获取存储的信息System.out.println("刚刚保存的值:"+ops.get("name"));}

查看可以通过Redis的客户端连接查看

image.png

也可以通过工具查看

image.png

4.改造三级分类

  在首页查询二级和三级分类数据的时候我们可以通过Redis来缓存存储对应的数据,来提升检索的效率。

@Overridepublic Map<String, List<Catalog2VO>> getCatelog2JSON() {// 从Redis中获取分类的信息String catalogJSON = stringRedisTemplate.opsForValue().get("catalogJSON");if(StringUtils.isEmpty(catalogJSON)){// 缓存中没有数据,需要从数据库中查询Map<String, List<Catalog2VO>> catelog2JSONForDb = getCatelog2JSONForDb();// 从数据库中查询到的数据,我们需要给缓存中也存储一份String json = JSON.toJSONString(catelog2JSONForDb);stringRedisTemplate.opsForValue().set("catalogJSON",json);return catelog2JSONForDb;}// 表示缓存命中了数据,那么从缓存中获取信息,然后返回Map<String, List<Catalog2VO>> stringListMap = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catalog2VO>>>() {});return stringListMap;}

  然后对三级分类的数据做压力测试

压力测试内容压力测试的线程数吞吐量/s90%响应时间99%响应时间
Nginx507,3851070
Gateway5023,170314
单独测试服务5023,16037
Gateway+服务508,4611246
Nginx+Gateway50
Nginx+Gateway+服务502,8162742
一级菜单501,3214874
三级分类压测501240004000
三级分类压测(业务优化后)50448113227
三级分类压测(Redis缓存)5011634959

  通过对比可以看到Redis缓存加入后的性能提升的效果还是非常明显的。

image.png

5.缓存穿透

  指查询一个一定不存在的数据,由于缓存是不命中,将去查询数据库,但是数据库也无此记录,我们没有将这次查询的null写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义.

image.png

利用不存在的数据进行攻击,数据库瞬时压力增大,最终导致崩溃,解决方案也比较简单,直接把null结果缓存,并加入短暂的过期时间

image.png

6.缓存雪崩

  缓存雪崩是指在我们设置缓存时key采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。

image.png

解决方案:原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
注意这里的随机数要取正数,这里有可能随机出负数,那么有效期时间就是无效的会报异常

image.png

7.缓存击穿

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

image.png

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

image.png

但是当我们压力测试的时候,输出的结果有点出乎我们的意料

image.png

做了两次的查询,原因是释放锁和查询结果缓存的时序问题

image.png

我们只需要调整下释放锁和结果缓存的时序问题就可以了

image.png

然后就是完整的代码处理

/*** 查询出所有的二级和三级分类的数据* 并封装为Map<String, Catalog2VO>对象* @return*/@Overridepublic 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;} }

8.本地锁的局限

  本地锁在分布式环境下,是没有办法锁住其他节点的操作的,这种情况肯定是有问题的

image.png

针对本地锁的问题,我们需要通过分布式锁来解决,那么是不是意味着本身锁在分布式场景下就不需要了呢?

image.png

  显然不是这样的,因为如果分布式环境下的每个节点不控制请求的数量,那么分布式锁的压力会非常大,这时我们需要本地锁来控制每个节点的同步,来降低分布式锁的压力,所以实际开发中我们都是本地锁和分布式锁结合使用的

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

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

相关文章

无涯教程-Python机器学习 - Stochastic Gradient Boosting函数

它也称为梯度提升机。在下面的Python食谱中,我们将通过使用pima Indians糖尿病数据集上的 sklearn 的 GradientBoostingClassifier 类来创建随机梯度Boostingensemble模型进行分类。 首先,导入所需的软件包,如下所示: from pandas import read_csv from sklearn.model_select…

万户协同办公平台 ezoffice存在未授权访问漏洞 附POC

文章目录 万户协同办公平台 ezoffice存在未授权访问漏洞 附POC1. 万户协同办公平台 ezoffice简介2.漏洞描述3.影响版本4.fofa查询语句5.漏洞复现6.POC&EXP7.整改意见8.往期回顾 万户协同办公平台 ezoffice存在未授权访问漏洞 附POC 免责声明&#xff1a;请勿利用文章内的相…

第9章:聚类

聚类任务 性能度量 距离度量 非度量距离 原型聚类 有很好的统计学上的意义&#xff0c;但是只能找到椭球形的聚类。 密度聚类 层次聚类

信号和槽的相关操作

目录 信号和槽 connect()函数 自定义信号槽 例子 自定义信号槽需要注意的事项 信号槽的更多用法 Lambda表达式 ① 函数对象参数 ② 操作符重载函数参数 ③ 可修改标示符 ④ 错误抛出标示符 ⑤ 函数返回值 ⑥ 是函数体 所谓信号槽&#xff0c;实际就是观察者模式。当…

Mac Flutter web环境搭建

获取 Flutter SDK 下载以下安装包来获取最新的 stable Flutter SDK将文件解压到目标路径, 比如: cd ~/development $ unzip ~/Downloads/flutter_macos_3.13.0-stable.zip 配置 flutter 的 PATH 环境变量&#xff1a; export PATH"$PATH:pwd/flutter/bin" // 这个命…

RSA私钥解密操作

RSA私钥解密操作 一、背景二、操作三、常见问题3.1 invalid key format3.2 解密的数据太长3.3 Decryption error 一、背景 项目数据库中存放的敏感字段已使用rsa加密的方式&#xff0c;将内容加密成密文存放, 现在需要在使用的时候&#xff0c;使用私钥进行解密。 二、操作 …

cublas_v2.h没有那个文件和目录,解决

我的是orin&#xff0c;使用的cuda11.4&#xff0c;后来发现通过sudo jetson_release看到的CUDA是没有安装的。 定位到问题是&#xff1a; 使用ls /usr/local/ -lha查看软连接&#xff0c;如下&#xff1a; 能够发现cuda这个软连接是有问题的&#xff0c;他链接的是cuda10.2 …

网页接口导入postman进行接口请求

postman版本&#xff1a;v10.17.4 一、拷贝接口信息 网页打开开发者工具-networkk&#xff0c;在网页上请求一次接口&#xff0c;鼠标指在接口上&#xff0c;点击鼠标右键-copy-copy as cURL(bash) 二、导入postman 打开postman&#xff0c;点击import-Raw text&#xff0c;…

mysql--数据库的操作

数据库&#xff0c;是数据存储的最大单元。 1 创建数据库 create database mydatabase; 每次创建数据库的时候&#xff0c;都会多一个文件夹&#xff0c;关系型数据库是存储在磁盘当中的&#xff0c;所以这时候可以查看新建的数据库 2 指定字符集 MySQL中的字符集转换过程 制…

zookeeper启动失败(Error contacting service. It is probably not running.)

问题描述 启动zk时报如下错误&#xff1a; 解决办法 先查日志找找报错原因&#xff1a; 找到zk安装目录下的logs文件夹下的日志文件&#xff0c;查看连接失败原因&#xff1a; 如果是端口问题&#xff0c;修改conf文件&#xff0c;指定端口重新启动即可&#xff1a; 注&a…

解决华为云ping不通的问题

进入华为云控制台。依次选择&#xff1a;云服务器->点击服务器id->安全组->更改安全组->添加入方向规则&#xff0c;添加一个安全组规则&#xff08;ICMP&#xff09;&#xff0c;详见下图 再次ping公网ip就可以ping通了 产生这一问题的原因是ping的协议基于ICMP协…

Python爬虫网络安全:优劣势和适用范围分析

各位Python程序猿大佬们&#xff01;在当今数字化时代&#xff0c;网络安全是至关重要的。保护你的网络通信安全对于个人和组织来说都是非常重要的任务。在本文中&#xff0c;我将与你一起探讨Python网络安全编程中的代理、虚拟专用网络和TLS这三个关键概念&#xff0c;分析它们…

回归预测 | MATLAB实现FA-ELM萤火虫算法优化极限学习机多输入单输出回归预测(多指标,多图)

回归预测 | MATLAB实现FA-ELM萤火虫算法优化极限学习机多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现FA-ELM萤火虫算法优化极限学习机多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09;效果一览基本介绍…

web、HTTP协议

目录 一、Web基础 1.1 HTML概述 1.1.1 HTML的文件结构 1.2 HTML中的部分基本标签 1.3 URI 和 URL 二.HTTP协议 2.1.HTTP概念 2.2.HTTP协议版本 2.3.HTTP请求方法 2.4.HTTP请求访问的完整过程 2.5.HTTP状态码 2.6.HTTP请求报文和响应报文 2.7.HTTP连接优化 三.HTT…

深入了解fcntl函数:Linux系统编程中的文件控制

文章目录 概述介绍函数原型与参数 拓展&#xff1a;fcntl改文件属性总结 概述 摘要: fcntl函数是Linux系统编程中一个重要的函数&#xff0c;用于对文件描述符进行各种控制操作。本文将详细介绍fcntl函数的原型、各个参数的用法&#xff0c;以及阻塞和非阻塞模式切换的方法&am…

YARN资源管理框架论述

一、简介 为了实现一个Hadoop集群的集群共享、可伸缩性和可靠性&#xff0c;并消除早期MapReduce框架中的JobTracker性能瓶颈&#xff0c;开源社区引入了统一的资源管理框架YARN。 YARN是将JobTracker的两个主要功能&#xff08;资源管理和作业调度/监控&#xff09;分离&…

Spring Boot 整合MyBatis(超详细)

&#x1f600;前言 本篇博文关于Spring Boot 整合MyBatis&#xff0c;希望你能够喜欢 &#x1f3e0;个人主页&#xff1a;晨犀主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是晨犀&#xff0c;希望我的文章可以帮助到大家&#xff0c;您的满意是我的动力&#x…

前端console.log打印内容与后端请求返回数据不一致

后端传值num0 前端打印num1 ,如图&#xff0c;console.log后台显示的数据与展开后不一致 造成该问题原因是深拷贝与浅拷贝的问题。 var obj JSON.parse(JSON.stringify(res)) 修改后打印 正常

Linux:LAMP架构与论坛搭建

目录 一、动态资源与语言 二、LAMP 架构的组成 三、CGI和astcgi 3.1CGI​​​​​​ 3.2fastcgi 3.3CGI和fastcgi比较 3.4 PHP 配置 3.5 Opcode语言 四、编译安装Apache http 服务 五、安装论坛 一、动态资源与语言 WEB 资源类型&#xff1a; 静态资源&#xff1a;原…

xsschallenge通关(1-10)

文章目录 level1level 2level 3level 4level 5level 6level 7level 8level9level 10 level1 这一关很简单&#xff0c;标准的xss注入&#xff0c;打开hackbar&#xff0c;输入 <script>alert(/xss/)</script>点击EXECUTE&#xff0c;通关&#xff01; level 2 这…