开发辅助三(缓存Redisson分布式锁+分页插件)

缓存

缓存穿透:查询一个不存在的数据,由于缓存不命中,将大量查询数据库,但是数据库也没有此记录。
没有将这次查询的null写入缓存,导致了这个不存在的数据每次请求都要到存储层查询,失去了缓存的意义。
解决:null结果缓存,并加入短暂的过期事件

缓存雪崩:设置缓存时采用了相同的过期时间,导致缓存存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重
解决:原有的失效时间基础上增加一个随机值,这样缓存的过期时间重复率就会降低。

缓存击穿:对于一些设置了过期时间的key,如果这些key在某些时间点高并发地访问,同时正好失效,那么所有的压力都会到数据库上。
解决:大量并发只让一个去查,其他人等待,查到以后释放锁,其他人获取到锁,先查缓存,就有数据,不用去数据库。
将redis作为缓存工具
在这里插入图片描述

①、模块中导入依赖,并且配置文件application.yml添加reids连接信息IP和端口

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

②、Service业务类中使用

@Autowired
private StringRedisTemplate redisTemplate;

@Override
public Map<String,List<Categlo2Vo>> getCatelogJson(){
	
	String catelogJSON = redisTemplate.opsForValue().get("catelogJSON");
	if(!StringUtils.isEmpty(catalogJSON)){
		//如果redis不存在,则从数据库中查找,然后将数据库查找的结果放入缓存redis中
		Map<String,List<Catelog2Vo>> catalogJsonFromDb = getCatalogJsonFromDb();
		
		return catalogJsonFromDb;
	}
	//如果缓存中存在json,则需要转为对象
	Map<String,List<Catelog2Vo>> result = JSON.parseObject(catalogJSON,new TypeReference<Map<String,List<Catelog2Vo>>>(){});
	return result;
}

//查询数据库
public Map<String,List<Catelog2Vo>> getCatalogJsonFromDb(){
	
	synchronized(this){
		//得到锁以后,应该再去缓存确定一次,如果没有才需要继续查询
		String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");
		if(!StringUtils.isEmpty(catalogJSON)){
			Map<String,List<Catelog2Vo>> result = JSON
				.parseObject(catalogJSON,new TypeReference<Map<String,List<Catelog2Vo>>>(){});
			return result;
		}
		
		//查询数据库的业务逻辑……
		//...
		

		//缓存中以json类型存储可以跨平台使用,所以将对象转为json,并放到缓存中
		String s = JSON.toJSONString(catalogJsonFromDb);
		redisTemplate.opsForValue().set("catalogJSON",s,1,TimeUnit.DAY);

		return parent_cid;
	}
	
}

产生堆外异常OutOfDirectMemoryError
SpringBoot2.0以后默认使用lettuce作为操作redis的客户端,使用netty进行网络通信
lettuce的bug导致netty堆外内存溢出,-Xmx300m
解决方案:①、升级lettuce客户端 ②、切换jedis

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
	<exclusions>
		<exclusion>
			<groupId>io.lettuce</groupId>
			<artifactId>lettuce-core<artifactId>
		</exclusion>
	</exclusions>
</dependency>
<dependency>
	<groupId>redis.clients</groupId>
	<artifactId>jedis</artifactId>
</denpendency>

分布式锁

redis命令

set locl 1111 NX

set lock 1111 EX 300 NX #占坑的同时添加了过期时间

①、若查询数据库业务中出现异常,导致delete锁无法执行,出现死锁情况,则通过添加expire过期时间来释放锁

public Map<String,List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock(){
	
	//占分布式锁(如果不存在,即设置)
	Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock","111");
	if(lock){//加锁成功,执行业务
	
		redisTemplate.expire("lock",30,TimeUnit.SECONDS);//防止查询数据库业务中出现异常导致的死锁
		Map<String,List<Catelog2Vo>> dataFromDb = getDataFromDb();//即使业务出现异常,还会删除锁
		redisTemplate.delete("lock");
		return dataFromDb
	}else{ //加锁失败
		
		//重试
		return getCatalogJsonFromDbWithRedisLock();
	}
	
}

在这里插入图片描述
②、若在执行过期时间的时候出现异常,造成无法释放锁

public Map<String,List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock(){
	
	Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock","1111",300,Time.SECONDS);//占锁的时候同时设置过期时间
	if(lock){
		Map<String,List<Catelog2Vo>> dataFromDb = getDataFromDb();//即使业务出现异常,还会删除锁
		redisTemplate.delete("lock");
		return dataFromDb
	}else{
		return getCatalogJsonFromDbWithRedisLock();
	}
}

在这里插入图片描述

③、删除锁导致将其他线程锁也删除,通过判断先获取自己的锁值,然后在删除自己的锁

public Map<String,List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock(){
	
	String uuid = UUID.randomUUID().toString();
	Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock",uuid,300,Time.SECONDS);//占锁的时候同时设置过期时间
	if(lock){
		Map<String,List<Catelog2Vo>> dataFromDb = getDataFromDb();

		String lockValue = redisTemplate.opsForValue().get("lock");
		if(uuid.equals(lockValue)){
			redisTemplate.delete("lock");
		}
		
		return dataFromDb;
	}else{
		return getCatalogJsonFromDbWithRedisLock();
	}
}

在这里插入图片描述

④、如果判断之后出现锁过期,而其他线程设置了锁,那么删除的依然是其他线程的锁(加锁、解锁都保证原子性)

public Map<String,List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock(){
	
	String uuid = UUID.randomUUID().toString();
	Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock",uuid,300,Time.SECONDS);//占锁的时候同时设置过期时间
	if(lock){
		Map<String,List<Catelog2Vo>> dataFromDb;
		try{
			dataFromDb = getDataFromDb();
		}finally{
			//通过lua脚本解锁
			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,Integer.class),
				Arrays.asList("lock"),uuid);
		}
		
		return dataFromDb;
	}else{
		return getCatalogJsonFromDbWithRedisLock();
	}
}

在这里插入图片描述

分布式锁Redisson

①、导入依赖

<dependency>
	<groupId>org.redisson</groupId>
	<artifactId>redisson</artifactId>
	<version>3.12.0</version>
</dependency>

②、配置类

@Configuration
public class MyRedissonConfig{
	
	@Bean(destroyMethod="shutdown")
	public RedissonClient redisson() throws IOException{
		Config config = new Config();
		/**
			Redis集群模式
			config.useClusterServers()
			.addNodeAddress("127.0.0.1:7004","127.0.0.1:7001");
		*/
		//Redis单节点模式
		config.useSingleServer().setAddress("redis://192.168.56.10:6379");
		RedissonClient redissonClient = Redisson.create(config);
		return redissonClient;
	}
}

③、使用

@Autowired
private RedissonClient redisson;

public Map<String,List<Catelog2Vo>> getCatalogJsonFromDbWithRedissonLock(){
	
	//获取锁,锁的粒度越小越好
	RLock lock = redisson.getLock("catalogJson-lock");
	lock.lock();
	
	Map<String,List<Catelog2Vo>> dataFromDb;
	try{
		dataFromDb = getDataFromDb();
	}finally{
		lock.unlock();
	}
	return dataFromDb;
}

缓存一致性

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

整合SpringCache

①、引入依赖

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

②、配置application.properties,配置使用redis作为缓存

spring.cache.type=redis
spring.cache.redis.time-to-live=1000 #设置过期时间,以毫秒为单位
spring.cache.redis.key-prefix=CACHE_
spring.cache.redis.use-key-prefix=true

#是否缓存空值(防止缓存穿透)
spring.cache.redis.cache-null-value=true

③、使用

先在启动类开启缓存注解 @EnableCaching

Service实现的业务类中:

@Cacheable(value={"category"},key="'level1Categorys'") //当前方法的结果需要缓存,如果缓存中有,方法不用调用
@Override
public List<CategoryEntity> getLevel1Categorys(){
	
	Long l = System.currentTimeMllis();
	List<CategoryEntity> categoryEntities = baseMapper.selectList();
	return categoryEntities;
}

@CacheEvict(value="category",key="getLevel1Categorys")//修改时,删除缓存
@Transactional
@Override
public void updateCascade(CategoryEntity category){
	
}

自定义缓存配置类

@EnableConfigurationProperties(CacheProperties.class) //开启和配置文件的绑定功能,使得配置文件的内容生效
@Configuration
@EnableCaching
public class MyCacheConfig{
	
	/**
		第一种方式:直接从容器的配置文件获取
		第二种方式:将其作为方法参数(如下方法的参数)
	*/
//	@Autowired
//	CacheProperties cacheProperties;//从容器中获取配置
	
	@Bean
	public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties){
		
		RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();

		//对key和value进行序列化
		config = config.serializeKeysWith(RedisSerializetionContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
		config = config.serializeValuesWith(RedisSerializetionContext.SerializationPair.fromSerializer(new StringRedisSerializer(new GenericJackson2JsoRedisSerializer()));

		//将配置文件中的所有配置生效
		CacheProperties.Redis redisProperties = cacheProperties.getRedis();
		if(redisProperties.getTimeToLive()!=null){
			config = config.entityTtl(redisProperties.getTimeToLive());
		}
		if(redisProperties.getKeyPrefix()!=null){
			config = config.prefixKeysWith(redis.getKeyPrefix());
		}
		if(!redisProperties.isCacheNullValues()){
			config = config.disableCachingNullValues();
		}
		if(!redisProperties.isUserKeyPrefix){
			config = config.disableKeyPrefix();
		}

		return config;
	}
}

分页插件

查询用户分页显示功能

①、myatis分页插件依赖

<dependency>
	<groupId>com.github.pagehelper</groupId>
	<artifactId>pagehelper</artifactId>
</dependency>

②、配置文件spring-persist-mybtis.xml

<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactory">
	<property name="configLocation" value="classpath:mybatis-config.xml"/>
	<property name="mapperLocations" value="classpath:mybatis/mapper/*Mapper.xml"/>
	<property name="dataSource" ref="dataSource"/>
	
	<!--配置插件-->
	<property name="plugins">
		<array>
			<!--分页插件-->
			<bean class="com.github.pagehelper.PageHelper">
				<property name="properties">
					<props>
						<!--数据库方言-->
						<prop key="dialect">mysql</prop>
						<!--配置页码的合理化修正,在1~总页数之间修正页码-->
						<prop key="reasonable">true</prop>
					</props>
				</property>
			</bean>
		</array>
	</property>
</bean>

③、Mapper文件和Mapper接口

<select id="selectAdminByKeyword" resultMap="BaseResultMap">
	select id,login_acct,user_pswd,user_name,email,create_time
	from t_admin
	where 
	login_acct like concat("%",#{keyword},"%") or 
	user_name like concat("%",#{keyword},"%") or
	email like concat("%",#{keyword},"%")
</select>
List<Admin> selectAdminByKeyword(String keyword);

④、service实现

@Override
public PageInfo<Admin> getPageInfo(String keyword,Integer pageNum,Integer pageSize){
	
	//调用PageHelper静态方法开启分页功能
	PageHelper.startPage(pageNum,pageSize);

	//执行查询
	List<Admin> list = adminMapper.selectAdminByKeyword(keyword);

	//封装到PageInfo对象中
	return new PageInfo<>(list);
}

⑤、Controller

@RequestMapping("/admin/get/page.html")
public String getPageInfo(@RequestParam(value="keyword",defaultValue="")String keyword,
						  @RequestParam(value="pageNum",defaultValue="1")Integer pageNum,
						  @RequestParam(value="pageSize",defaultValue="5")Integr pageSize,
						  ModelMap modelMap){
	
	PageInfo<Admin> pageInfo = adminService.getPageInfo(keyword,pageNum,pageSize);
	
	modelMap.addAttribute(CrowdConstant.ATTR_NAME_PAGE_INFO,pageInfo);
	return "admin-page"
}

⑥、页面中循环遍历

<c:if test="${empty requestScope.pageInfo.list}">
	<tr>
		<td colspan="6" align="center">没有查询到你要的数据</td>
	</tr>
</c:if>
<c:if test="${!empty requestScope.pageInfo.list}">
	<c:forEach items="${requestScope.pageInfo.list}" var="admin">
		
	<c:forEach>
</c:if>

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

地平面--高速布线

https://baijiahao.baidu.com/s?id1764139038516816855&wfrspider&forpc 概念 回顾传输线&#xff0c;由任意两条有一定长度的导线组成&#xff0c;一条为信号路径&#xff0c;一条为返回路径。基本电路理论告诉我们&#xff0c;信号是由电流传播的&#xff0c;明确的…

.NET国产化改造探索(一)、VMware安装银河麒麟

随着时代的发展以及近年来信创工作和…废话就不多说了&#xff0c;这个系列就是为.NET遇到国产化需求的一个闭坑系列。接下来&#xff0c;看操作。 安装银河麒麟 麒麟系统分银河麒麟和中标麒麟&#xff0c;我选择的是银河麒麟服务器版的&#xff0c;关于如何下载&#xff0c;…

大数据StarRocks(一) StarRocks概述

1 StarRocks介绍 StarRocks是新一代极速全场景MPP(Massively Parallel Processing)数据库&#xff0c;它充分吸收关系型OLAP数据库和分布式存储系统在大数据时代的优秀研究成果&#xff0c;在业界实践的基础上&#xff0c;进一步改进优化、升级架构&#xff0c;并增添了众多全…

即插即用篇 | YOLOv8 Gradio 前端展示页面 | 支持 【分类】【检测】【分割】【关键点】 任务

分类任务效果 分割任务效果 检测任务效果 关键点任务效果 使用方法 Gradio 是一个开源库,旨在为机器学习模型提供快速且易于使用的网页界面。它允许开发者和研究人员轻松地为他们的模型创建交互式的演示,使得无论技术背景如何的人都可以方便地试用和理解这些模型。使用Gradi…

el-radio-button自适应充满盒子的写法

业务场景&#xff1a;当盒子较宽时&#xff0c;希望 el-radio-button 自适应充满盒子。 el-radio-button 自适应充满盒子的写法 <el-form :inline"true" :model"searchForm"><el-form-item style"display: flex; align-items: center;"…

Qt:自定义一个好看的等待提示Ui控件

一、2024 永不卡顿 爱的魔力它转圈圈~ 等待样式控件是我们在做UI时出场率还挺高的控件之一&#xff0c;通常情况下有如下的几种实现方式&#xff1a; 1> 获取一张gif的资源图&#xff0c;然后使用QMovie 在一个QLabel 控件上加载显示gif的waiting等待动态。 2> 自定义绘图…

怎么快速修复mfc140.dll文件?解决mfc140.dll缺失的方法

面对计算机报告的 ​mfc140.dll​ 文件遗失错误&#xff0c;这通常表明系统中缺少一个关键的动态链接库文件&#xff0c;该文件对于运行以 Microsoft Foundation Class (MFC) 库编写的程序十分重要&#xff0c;尤其是那些需要图形界面的应用程序和一些游戏。若没有这个文件&…

【数字图像处理技术与应用】2023-2024上图像处理期中-云南农业大学

一、填空题&#xff08;每空2 分&#xff0c;共 30 分&#xff09; 1、图像就是3D 场景在 二维 平面上的影像&#xff0c;根据其存储方式和表现形式&#xff0c;可以将图像分为 模拟 图像和数字图像两大类&#xff1b; 2、在用计算机对数字图像处理中&#xff0c;常用一个 二…

【userfaultfd 条件竞争】starCTF2019 - hackme

前言 呜呜呜&#xff0c;这题不难&#xff0c;但是差不多一个多月没碰我的女朋友 kernel pwn 了&#xff0c;对我的 root 宝宝也是非常想念&#xff0c;可惜这题没有找到我的 root 宝宝&#xff0c;就偷了她的 flag。 哎有点生疏了&#xff0c;这题没看出来堆溢出&#xff0c…

引导过程的解析以及教程za

bios加电自检------mbr--------grub-------加载内核文件------启动第一个进程 bios的主要作用&#xff1a;检测硬件是否正常&#xff0c;然后根据bios中的启动项设置&#xff0c;去找内核文件 boot开机启动项顺序&#xff0c;你可以把内核文件放在何处&#xff1f; 1.硬盘 …

Vue3 watch 的使用,如何监听一个对象中的属性值的变化 vue3 + ts + vite

Vue3 watch 的使用&#xff0c;如何监听一个对象中的属性值的变化 由 vue2 转到 vue3 ts vite 之后都不会写这些玩意了。搜了下&#xff0c;找到了答案&#xff1a; vue2 的 watch export default {watch: {$route.query.id(newValue){// 可以这样监听路由的变化},formUse…

CGAL的D维空间搜索(基于KD树)

1、介绍 空间搜索包通过提供支持以下算法的实现来实现精确和近似距离的实现 最近和最远邻居搜索 精确和近似搜索 &#xff08;近似&#xff09;范围搜索 &#xff08;近似&#xff09;k-最近邻和k-最远邻搜索 &#xff08;近似&#xff09;增量最近邻和增量最远邻搜索 表示点和…

SPSS23软件安装包下载及安装教程

SPSS 23下载链接&#xff1a;https://docs.qq.com/doc/DUkRHVUxFUkVBUUlj 1.选中下载好的安装包&#xff0c;鼠标右键解压到“SPSS 23 64bit”文件夹 2.打开”Setup“文件夹 3.选中”Setup.exe“鼠标右击选择以管理员身份运行 4.点击“下一步” 5.点击“下一步” 6.选择“我接受…

安全与认证Week4

目录 本章需要理解的问题 Web Security (TLS/SSL) 关于网络 使用网络会受到的威胁 各层安全协议 S/MIME、PGP&#xff08;后面和S/MIME一起出现&#xff09;、Kerberos、TLS/SSL 和 IP/IPSec 分别是&#xff1a; S/MIME (Secure/Multipurpose Internet Mail Extensions)&#x…

Redis缓存保卫战:拒绝缓存击穿的进攻【redis问题 三】

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 Redis缓存保卫战&#xff1a;拒绝缓存击穿的进攻 前言缓存击穿的定义和原理为何会发生缓存击穿缓存击穿的危害防范缓存击穿结语: 前言 你是否曾经遇到过系统在高并发情况下出现严重性能问题&#xff…

【计算机图形学】NAP: Neural 3D Articulation Prior

文章目录 1. 这篇论文做了什么事&#xff0c;有什么贡献&#xff1f;2. Related Work铰接物体建模3D中的Diffusion model扩散模型 3. Pipeline铰接树参数化基于Diffusion的铰接树生成去噪网络 4. 实验评价铰接物体生成——以往做法与本文提出的新指标NAP捕捉到的铰接物体分布质…

用户管理第2节课--idea 2023.2 后端--实现基本数据库操作(操作user表) -- 自动生成 --【本人】

一、插件安装 1.1 搜索插件 mybatis 安装 1.2 接受安装 1.3 再次进入&#xff0c;说明安装好了 1.4 与鱼皮不同点 1&#xff09;mybatis 版本不一致 鱼皮&#xff1a; 本人&#xff1a; 2&#xff09;鱼皮需重启安装 本人不需要 1.5 【需完成 三、步骤&#xff0c;再来看】 …

Noisy DQN 跑 CartPole-v1

gym 0.26.1 CartPole-v1 NoisyNet DQN NoisyNet 就是把原来Linear里的w/b 换成 mu sigma * epsilon, 这是一种非常简单的方法&#xff0c;但是可以显著提升DQN的表现。 和之前最原始的DQN相比就是改了两个地方&#xff0c;一个是Linear改成了NoisyLinear,另外一个是在agent在t…

车载 Android之 核心服务 - CarPropertyService 解析

重要类的源码文件名及位置&#xff1a; CarPropertyManager.java packages/services/Car/car-lib/src/android/car/hardware/property/ CarPropertyService.java packages/services/Car/service/src/com/android/car/ 类的介绍&#xff1a; CarPropertyManager&#xff1a…

基于多反应堆的高并发服务器【C/C++/Reactor】(中)在EventLoop中处理被激活的文件描述符的事件

文件描述符处理与回调函数 一、主要概念 反应堆模型&#xff1a;一种处理系统事件或网络事件的模型&#xff0c;当文件描述符被激活时&#xff0c;可以检测到文件描述符&#xff1a;在操作系统中&#xff0c;用于标识打开的文件、套接字等的一种数据类型 处理激活的文件描述符…