Redis之商品缓存

news2024/11/15 11:44:04

文章目录

  • 什么是缓存
  • 添加Redis缓存
  • 缓存更新策略
  • 缓存穿透
    • 缓存空对象
    • 布隆过滤器
  • 缓存雪崩
    • 给不同的key的TTL添加随机值
    • 利用Redis集群提高服务的可用性
    • 给缓存业务添加降级限流策略
    • 给业务添加多级缓存
  • 缓存击穿
    • 互斥锁
    • 逻辑过期
  • 缓存工具封装
    • 方法1 写入redis
    • 方法2 设置逻辑过期
    • 方法3 解决缓存穿透
    • 方法4 解决缓存击穿

什么是缓存


缓存就是数据交换的缓冲区( Cache ), 是存储数据的临时地方, 一般读写性能较高

缓存的作用:

  • 降低后端负载
  • 提高读写效率, 降低响应时间

缓存的成本:

  • 数据一致性成本
  • 代码维护成本
  • 运维成本

添加Redis缓存


一图胜过千言万语~
image.png

/**
 * 根据id查询商铺信息
 * @param id 商铺id
 * @return 商铺详情数据
 */
@GetMapping("/{id}")
public Result queryShopById(@PathVariable("id") Long id) {
    return Result.ok(shopService.queryById(id));
}

@Override
public Object queryById(Long id) {
	String shopKey = CACHE_SHOP_KEY + id;
	// 从redis查询商店缓存
	String shopJson = stringRedisTemplate.opsForValue().get(shopKey);
	// 判断缓存是否存在
	if(StrUtil.isNotBlank(shopJson)){
		// 缓存存在,直接返回
		Shop shop = JSONUtil.toBean(shopJson, Shop.class);
		return Result.ok(shop);
	}
	// 缓存不存在,查询数据库
	Shop shop = getById(id);
	// 店铺不存在, 返回错误
	if(shop == null){
		return Result.fail("商铺不存在");
	}
	// 店铺存在, 写入redus
	stringRedisTemplate.opsForValue().set(shopKey, JSONUtil.toJsonStr(shop));
	// 返回
	return Result.ok(shop);
}
  1. 从redis中查询是否存在商铺缓存
  • stringRedisTemplate.opsForValue().get(shopKey)
  1. 缓存存在, 将数据转换成Java对象并返回
  • Shop shop = JSONUtil.toBean(shopJson, Shop.class)
  1. 缓存不存在, 先查库看商铺是否存在
  • Shop shop = getById(id)
  1. 商铺不存在, 直接返回错误提示
  • return Result.fail("商铺不存在")
  1. 商铺存在, 写入redis再返回商铺信息
  • stringRedisTemplate.opsForValue().set(shopKey, JSONUtil.toJsonStr(shop))
  • return Result.ok(shop)

缓存更新策略


image.png

业务场景:

  • 低一致性要求: 使用内存淘汰机制. 例如店铺类型的查询缓存
  • 高一致性要求: 主动更新, 并以超时剔除作为兜底方案. 例如店铺详情查询的缓存
  • 读操作:
    • 缓存命中则直接返回
    • 缓存未命中则查数据库, 并写入缓存, 设定超时时间
  • 写操作:
    • 先写数据库, 然后再删除缓存
    • 要确保数据库与缓存操作的原子性

更新商铺信息

/**
 * 更新商铺信息
 * @param shop 商铺数据
 * @return 无
 */
@PutMapping
public Result updateShop(@RequestBody Shop shop) {
    // 写入数据库
    return shopService.updateShop(shop);
}

@Override
@Transactional
public Result updateShop(Shop shop) {
	Long id = shop.getId();
	if(id == null){
		return Result.fail("商铺ID不能为空");
	}
	// 更新数据库
	updateById(shop);
	// 删除缓存
	stringRedisTemplate.delete(CACHE_SHOP_KEY + id);
	return Result.ok();
}
  1. 判断商铺ID是否为空, 防止NPE
  2. 更新数据库, 直接调用MP的方法, 最后在删除缓存
  3. 添加@Transactional注解, 保持事务一致性, 防止缓存与数据库不一致

缓存穿透


缓存穿透:
指客户端请求的数据在缓存中和数据库都不存在,
这样缓存永远不会生效, 这些请求都会发送到数据库

image.png

解决方案:

缓存空对象

  • 优点: 实现简单 维护方便
  • 缺点: 额外内存消耗 可能造成短期数据不一致

布隆过滤器

  • 优点: 内存占用较少 没有多余key
  • 缺点: 实现复杂 存在误判可能

image.png

@Override
public Object queryById(Long id) {
	String shopKey = CACHE_SHOP_KEY + id;
	// 从redis查询商店缓存
	String shopJson = stringRedisTemplate.opsForValue().get(shopKey);
	// 判断缓存是否存在
	if(StrUtil.isNotBlank(shopJson)){
		// 缓存存在,直接返回
		Shop shop = JSONUtil.toBean(shopJson, Shop.class);
		return Result.ok(shop);
	}
	// 判断命中的是否是空值
***	if(shopJson != null){							<***为多加的代码,防止缓存穿透>
***		// 命中空值,直接返回
***		return Result.fail("商铺不存在");
***	}
	// 缓存不存在,查询数据库
	Shop shop = getById(id);
	// 店铺不存在, 返回错误
	if(shop == null){
		// 将空值写入redis
***		stringRedisTemplate.opsForValue().set(shopKey, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
		// 返回错误信息
		return Result.fail("商铺不存在");
	}
	// 店铺存在, 写入redus
	stringRedisTemplate.opsForValue().set(shopKey, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);
	// 返回
	return Result.ok(shop);
}

缓存雪崩


缓存雪崩:
指同一时段大量缓存的key同时失效或者Redis服务器宕机
导致大量请求到达数据库, 带来巨大请求压力

image.png

解决方案:

给不同的key的TTL添加随机值

利用Redis集群提高服务的可用性

给缓存业务添加降级限流策略

给业务添加多级缓存

缓存击穿


缓存击穿:
缓存击穿问题也叫热点Key问题, 一个被高并发访问并且缓存重建业务较复杂的key突然失效
无数的请求访问会在瞬间给数据库带来巨大的冲击

image.png

解决方案:

互斥锁

  • 优点:
    • 没有额外的内存消耗
    • 保证一致性
    • 实现简单
  • 缺点:
    • 线程需要等待 性能受到影响
    • 可能有死锁的风险

逻辑过期

  • 优点:
    • 现成无需等待
    • 性能较好
  • 缺点:
    • 不保证一致性
    • 有额外的内存消耗
    • 实现复杂

互斥锁解决缓存击穿
image.png

互斥锁解决缓存击穿

// 缓存击穿
public Shop queryWithMutex(Long id){
	String shopKey = CACHE_SHOP_KEY + id;
	// 从redis查询商店缓存
	String shopJson = stringRedisTemplate.opsForValue().get(shopKey);
	// 判断缓存是否存在
	if(StrUtil.isNotBlank(shopJson)){
		// 缓存存在,直接返回
		return JSONUtil.toBean(shopJson, Shop.class);
	}
	// 判断命中的是否是空值
	if(shopJson != null){
		// 命中空值,直接返回
		return null;
	}
	// 实现缓存重建
	// 1. 获取互斥锁
	String lockKey = "lock:shop:" + id;
	Shop shop = null;
	try{
		boolean isLock = tryLock(lockKey);
		// 2. 判断是否获取成功
		if(!isLock){
			// 3. 失败 则休眠并重试
			Thread.sleep(50);
			return queryWithMutex(id);
		}
		// 4. 成功 根据id查询数据库
		shop = getById(id);
		// 模拟重建延时
		Thread.sleep(1000);
		// 店铺不存在, 返回错误
		if(shop == null){
			// 将空值写入redis
			stringRedisTemplate.opsForValue().set(shopKey, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
			// 返回错误信息
			return null;
		}
		// 店铺存在, 写入redus
		stringRedisTemplate.opsForValue().set(shopKey, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);
	}catch(InterruptedException e){
		throw new RuntimeException(e);
	}finally{
		// 释放互斥锁
		unLock(lockKey);
	}
	// 返回
	return shop;
}

// 获取锁
private boolean tryLock(String key){
	Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
	return BooleanUtil.isTrue(flag);
}
// 释放锁
private void unLock(String key){
	stringRedisTemplate.delete(key);
}
  1. 判断缓存是否存在, 存在直接返回, 不存在先判断是否为空值, 空值返回null, 不是空值则继续
  2. 实现缓存重建, 尝试拿到锁, 失败则休眠一段时间之后再次尝试拿锁~
  3. 成功拿到锁之后查询数据库, 判断数据是否存在, 不存在则写入空字符串到redis, 防止缓存穿透
  4. 数据存在则将数据写入redis, 下次查询直接命中缓存, 无需抢锁再查库, 最后缓存重建成功, 释放锁

使用逻辑过期解决缓存击穿问题
image.png

使用逻辑过期解决缓存击穿问题

// 数据预热 / 缓存重建
public void saveShopRedis(Long id, Long expireSeconds){
	// 1. 查询店铺数据
	Shop shop = getById(id);
	// 2. 封装逻辑过期时间
	RedisData redisData = new RedisData();
	redisData.setData(shop);
	redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));
	// 3. 写入Redis
	stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisData));
}

// 独立线程重建缓存(线程池)
private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
// 逻辑过期解决缓存击穿
public Shop queryWithLogicalExpire(Long id){
	String shopKey = CACHE_SHOP_KEY + id;
	// 1. 从redis查询商店缓存
	String shopJson = stringRedisTemplate.opsForValue().get(shopKey);
	// 2. 判断缓存是否存在
	if(StrUtil.isBlank(shopJson)){
		// 3. 缓存不存在,直接返回
		return null;
	}
	// 4. 命中, 需要先把json反序列化为对象
	RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);
	Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);
	LocalDateTime expireTime = redisData.getExpireTime();
	// 5. 判断是否过期
	if(expireTime.isAfter(LocalDateTime.now())){
		// 5.1 未过期, 直接返回
		return shop;
	}
	// 5.2 过期, 重建缓存
	// 6. 缓存重建
	// 6.1 获取互斥锁
	String lockKey = LOCK_SHOP_KEY + id;
	boolean isLock = tryLock(lockKey);
	// 6.2 判断是否获取成功
	if(isLock){
		// 6.3 成功, 开启独立线程, 实现缓存重建
		CACHE_REBUILD_EXECUTOR.submit(() -> {
			try {
				// 重建缓存
				this.saveShopRedis(id, 20L);
			} catch (Exception e) {
				throw new RuntimeException(e);
			} finally {
				// 释放互斥锁
				unLock(lockKey);
			}
		});
	}
	// 6.4 返回过期的商铺信息
	return shop;
}
  1. 判断redis是否存在缓存数据
  • stringRedisTemplate.opsForValue().get(shopKey)
  1. 判断缓存是否存在 if(StrUtil.isBlank(shopJson))
  2. 缓存不存在, 返回 null
  3. 缓存存在, 将缓存反序列化为Java对象
  • RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class)
  • Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class)
  • LocalDateTime expireTime = redisData.getExpireTime() -> 逻辑过期时间
  1. 判断逻辑时间是否过期 if(expireTime.isAfter(LocalDateTime.now()))
  2. 未过期直接返回缓存信息 return shop
  3. 过期则进行缓存重建, 要获取锁, boolean isLock = tryLock(lockKey)
  4. 抢锁成功则进行缓存重建
  • Shop shop = getById(id)
  • RedisData redisData = new RedisData()
  • String cacheKey = CACHE_SHOP_KEY + id
  • redisData.setData(shop)
  • redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds))
  • stringRedisTemplate.opsForValue().set(cacheKey, JSONUtil.toJsonStr(redisData))
  1. 缓存重建成功之后释放锁, unLock(), 最后返回信息 return shop

缓存工具封装


缓存工具封装
image.png

方法1 写入redis

// 写入缓存
public void set(String key, Object value, Long time, TimeUnit unit){
	stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);
}

方法2 设置逻辑过期

public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit){
	// 设置逻辑过期
	RedisData redisData = new RedisData();
	redisData.setData(value);
	redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
	// 写入Redis
	stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
}
  1. 拿到缓存的key, 缓存的数据value, 缓存时间time, 缓存时间单位unit
  2. 分别设置数据和过期时间, 过期时间是以当前时间加上缓存时间time
  • redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)))
  1. 最后写入Redis, 缓存数据转换成String类型

方法3 解决缓存穿透

// 解决缓存穿透
public <R, ID> R queryWithPassThrough(
		String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit){
	String key = keyPrefix + id;
	// 从redis查询商店缓存
	String json = stringRedisTemplate.opsForValue().get(key);
	// 判断缓存是否存在
	if(StrUtil.isNotBlank(json)){
		// 缓存存在,直接返回
		return JSONUtil.toBean(json, type);
	}
	// 判断命中的是否是空值
	if(json != null){
		// 命中空值,直接返回
		return null;
	}
	// 缓存不存在,查询数据库
	R r = dbFallback.apply(id);
	// 店铺不存在, 返回错误
	if(r == null){
		// 将空值写入redis
		stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
		// 返回错误信息
		return null;
	}
	// 店铺存在, 写入redus
	this.set(key, r, time, unit);
	// 返回
	return r;
}

方法4 解决缓存击穿

// 解决缓存击穿
// 独立线程重建缓存(线程池)
private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
// 逻辑过期解决缓存击穿
public <R, ID> R queryWithLogicalExpire(
		String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit){
	String key = keyPrefix + id;
	// 1. 从redis查询商店缓存
	String json = stringRedisTemplate.opsForValue().get(key);
	// 2. 判断缓存是否存在
	if(StrUtil.isBlank(json)){
		// 3. 缓存存在,直接返回
		return null;
	}
	// 4. 命中, 需要先把json反序列化为对象
	RedisData redisData = JSONUtil.toBean(json, RedisData.class);
	R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);
	LocalDateTime expireTime = redisData.getExpireTime();
	// 5. 判断是否过期
	if(expireTime.isAfter(LocalDateTime.now())){
		// 5.1 未过期, 直接返回
		return r;
	}
	// 5.2 过期, 重建缓存
	// 6. 缓存重建
	// 6.1 获取互斥锁
	String lockKey = LOCK_SHOP_KEY + id;
	boolean isLock = tryLock(lockKey);
	// 6.2 判断是否获取成功
	if(isLock){
		// 6.3 成功, 开启独立线程, 实现缓存重建
		CACHE_REBUILD_EXECUTOR.submit(() -> {
			try {
				// 查询数据库
				R r1 = dbFallback.apply(id);
				// 写入Redis
				this.setWithLogicalExpire(key, r1, time, unit);
			} catch (Exception e) {
				throw new RuntimeException(e);
			} finally {
				// 释放互斥锁
				unLock(lockKey);
			}
		});
	}
	// 6.4 返回过期的商铺信息
	return r;
}
// 获取锁
private boolean tryLock(String key){
	Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
	return BooleanUtil.isTrue(flag);
}
// 释放锁
private void unLock(String key){
	stringRedisTemplate.delete(key);
}

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

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

相关文章

Android集成高德地图SDK(1)

1.新建Android应用&#xff0c;确定应用包名 2.注册高德开放平台&#xff0c;打开控制台页面&#xff0c;应用管理&#xff0c;我的应用&#xff0c;创建新应用 3.添加Key 4.获取SHA1码 找到Android Studio自带的keytool 将其拖到cmd中&#xff0c;输入命令 -v -list -keystor…

Qt:2.环境搭建

目录 1.搭建需要的三个组件&#xff1a; 2.下载Qt安装包&#xff1a; 3.安装qt&#xff1a; 4.配置环境变量&#xff1a; 1.搭建需要的三个组件&#xff1a; C编译器&#xff08;gcc&#xff0c;cl.exe等&#xff09;Qt的SDK&#xff1a;软件开发工具包&#xff0c;Windows…

component-切换选中状态更改

1.例 实现默认选中初始值&#xff0c;点击选中对应状态。 2.代码 2.1 html <span click"ponIn()" class"market-switch" :style"{color: SelectedOne ? :#1890FF,borderBottom:SelectedOne ? :1px solid #1890FF}">业务1</span>…

适用于 Windows 11 的 5 大数据恢复软件 [免费和付费]

为什么我们需要Windows 11数据恢复软件&#xff1f; 计算机用户经常遇到的一件事就是数据丢失&#xff0c;这种情况随时可能发生。错误地删除重要文件和文件夹可能会非常令人担忧&#xff0c;但幸运的是&#xff0c;有一种方法可以恢复 PC 上丢失的数据。本文将向您展示可用于…

Servlet工作原理

Servlet 工作原理 编写Servlet 创建servlet 创建一个MyServlet继承HttpServlet&#xff0c;重写doGet和doPost方法&#xff0c;也就是看请求的方式是get还是post&#xff0c;然后用不同的处理方式来处理请求&#xff0c; 2. 配置Servlet //添加参数 <servlet><se…

【C++/STL】:优先级队列(priority_queue)的使用及底层剖析仿函数

目录 &#x1f4a1;前言一&#xff0c;优先级队列的使用二&#xff0c;仿函数1&#xff0c;什么是仿函数2&#xff0c;仿函数的简单示例 三&#xff0c;优先级队列的底层剖析 &#x1f4a1;前言 优先队列(priority_queue)是一种容器适配器&#xff0c;默认使用vector作为其底层…

小学数学蝴蝶模型详解

蝴蝶模型 1.蝴蝶模型仅存在于梯形中&#xff0c;是连接梯形两条对角线而形成的&#xff0c;如下图&#xff1a; 2.蝴蝶模型有几条公式 (1) (2) S△AODS△BOC 等等......

GPT-5的未来愿景:技术突破、智能协作与伦理道德考量

随着人工智能技术日新月异的进步&#xff0c;GPT-5已然崭露头角&#xff0c;它不仅预示着新一轮的技术风暴&#xff0c;更象征着自然语言处理与智能系统协作将迈入一个崭新的纪元。然而&#xff0c;在这一技术奇迹的背后&#xff0c;我们亦需审慎思考伦理道德及安全性问题。 技…

ChatGPT的原理简介

目录 前言 1. 什么是ChatGPT&#xff1f; 2. GPT模型的基本原理 自注意力机制 预训练和微调 3. ChatGPT的工作流程 4. ChatGPT的优势和挑战 5. 实例对话 6. 未来展望 结语 前言 在这个智能科技飞速发展的时代&#xff0c;聊天机器人逐渐成为我们生活中的“新朋友”。…

Go的GUI Fyne开发环境搭建—Windows 11

安装go 到官网下载安装go安装包 https://go.dev/learn/ 通过如下命令检验安装是否成功&#xff0c;出现版本号则安装成功 go version安装国内go依赖包代理 go env -w GOPROXYhttps://goproxy.cn安装gcc编译器 直接用官网提供的安装建议第二条&#xff0c;到这个地址进行下载…

mysql数据库索引的选择性

文章目录 索引的选择性索引选择性的计算单列索引的选择性计算值组合列索引的选择性计算值 索引列的两个基本要求 索引的选择性 是指不重复的索引值与表总记录数的比值&#xff0c;其范围(0,1]。通过索引的选择性&#xff0c;可以确定该索引是否合理(70%)。索引选择性的计算 表…

病毒防护:恶意代码检测技术,病毒分类、传播方式,恶意代码的清除与防护

「作者简介」&#xff1a;冬奥会网络安全中国代表队&#xff0c;CSDN Top100&#xff0c;就职奇安信多年&#xff0c;以实战工作为基础著作 《网络安全自学教程》&#xff0c;适合基础薄弱的同学系统化的学习网络安全&#xff0c;用最短的时间掌握最核心的技术。 这一章节我们需…

HarmonyOS Next开发学习手册——ExtensionAbility

概述 EmbeddedUIExtensionAbility 是EMBEDDED_UI类型的ExtensionAbility组件&#xff0c;提供了跨进程界面嵌入的能力。 EmbeddedUIExtensionAbility需要和 EmbeddedComponent 一起配合使用&#xff0c;开发者可以在UIAbility的页面中通过EmbeddedComponent嵌入本应用的Embed…

联邦学习——学习笔记2:联邦学习×资源受限下的自适应本地迭代次数

文章目录 一、符号二、应用场景三、与FedAvg算法区别 本笔记参考自b站up主&#xff1a;丸一口 论文参考自Adaptive Federated Learning in Resource Constrained Edge Computing Systems 原视频链接 一、符号 原文的符号解释如下图绿色字体所注 二、应用场景 就是在资源小…

【详述】BP神经网络建模流程一步一步详述

本文来自《老饼讲解-BP神经网络》https://www.bbbdata.com/ 目录 一、BP神经网络的建模流程二、BP神经网络的建模分步讲解2.1.数据归一化2.2.数据划分2.3.网络结构设置2.4.网络训练2.5.训练效果评估 本文梳理BP神经网络的建模流程&#xff0c;供大家建模时进行借鉴。 一、BP神经…

C# VTK 移动旋转

对vtk 场景中一个或多个选中物体进行移动旋转。 交互移动旋转坐标系 首先我们创建旋转的交互坐标系&#xff0c;三个移动Actor&#xff0c;三个旋转Actor&#xff0c;还需要4个定位坐标的小球Actor。 public class CoordinateActor 中添加Actor// 当前选中的Actorpublic vtkAc…

数据结构与算法:回溯算法约束条件:剪枝详解、示例(C#、C++)与回溯典型例题详解

文章目录 一、约束条件二、剪枝三、典型例题四、常用术语五、示例N 皇后问题 C# 示例N 皇后问题 C 示例 六、常见用用回溯算法解决的问题汇总组合问题&#xff1a;图论问题&#xff1a;棋盘游戏问题&#xff1a;优化问题&#xff1a;调度问题&#xff1a;其他问题&#xff1a; …

Study--Oracle-04-SQL练习

一、SQL语句思维导图 二、SQL练习 -- 以employee_id 为排序&#xff0c;列出前5个人 -- FETCH select employee_id,first_name from employees order by employee_id FETCH FIRST 5 rows only; -- 以employee_id 为排序&#xff0c;从第6个人开始 到第10个人 -- offset …

48、基于深度学习的离群值输入向量(matlab)

1、基于深度学习的离群值输入向量原理及流程 基于深度学习的离群值检测的输入向量原理是通过神经网络模型对数据进行学习和表示&#xff0c;在该表示中探测异常样本。其流程大致如下&#xff1a; 数据预处理&#xff1a;将数据进行归一化处理&#xff0c;确保神经网络模型能够…

【MDK5问题】:MDK5无法跳转,并且提示:no browse information available in xxxxx

1、问题&#xff1a; MDK5原来的函数调用可以直接跳转到原函数&#xff0c;但是出现不能跳转原函数的情况&#xff0c;且提示&#xff1a;no browse information available in xxxxx 的情况&#xff1b; 2、解决&#xff1a; 如下图所示&#xff1a;在魔术棒&#xff08;pro…