当前位置: 首页 > article >正文

Redis存储“大数据对象”的常用策略及StackOverflowError错误解决方案

Hi,大家好,我是灰小猿!

在一些功能的开发中,我们一般会有一些场景需要将得到的数据先暂时的存储起来,以便后面的接口或业务使用,这种场景我们一般常用的场景就是将数据暂时存储在缓存中,之后再从缓存获取,以支持高可用的分布式项目为例,可以通过以下步骤实现数据的临时存储和后续处理:

举例场景及解决方案

举例场景:以用户导入文件并解析文件数据,响应变更数据给用户,确认数据无误后存储的场景为例,需要对应两个接口:

文件解析接口:用户导入文件并解析文件数据,响应变更数据给用户

数据存储接口:确认数据无误后存储上一接口解析出来的文件数据

使用 Redis 临时存储文件解析出的DTO数据,结合 唯一Token标识 确保两次请求间的数据关联。具体步骤如下:


1. 用户上传文件并解析

接口设计

  • 请求方式POST /api/upload

  • 参数MultipartFile file

  • 返回:解析数据变更信息 + 唯一Token(用于后续操作)

代码实现

@PostMapping("/upload")
public ResponseEntity<ConflictResponse> handleFileUpload(@RequestParam("file") MultipartFile file) throws IOException {// 1. 解析文件生成DTOList<DataDTO> parsedData = fileParser.parse(file.getInputStream());// 2. 与数据库对比,生成冲突信息List<ConflictInfo> conflicts = dataComparator.compareWithDatabase(parsedData);// 3. 生成唯一Token(如UUID)String token = UUID.randomUUID().toString();// 4. 将DTO数据存入Redis,设置过期时间(如30分钟)redisTemplate.opsForValue().set("upload:data:" + token, parsedData, Duration.ofMinutes(30));// 5. 返回冲突信息和Tokenreturn ResponseEntity.ok(new ConflictResponse(conflicts, token));
}

2. 用户提交处理选择

接口设计

  • 请求方式POST /api/resolve

  • 参数ResolveRequest(包含Token和用户选择)

  • 返回:处理结果

代码实现

@PostMapping("/resolve")
public ResponseEntity<String> resolveConflicts(@RequestBody ResolveRequest request) {// 1. 从Redis中获取临时存储的DTO数据String redisKey = "upload:data:" + request.getToken();List<DataDTO> parsedData = (List<DataDTO>) redisTemplate.opsForValue().get(redisKey);if (parsedData == null) {return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("操作超时或Token无效,请重新上传文件");}// 2. ******中间业务数据处理******// 3. 清理Redis中的临时数据redisTemplate.delete(redisKey);return ResponseEntity.ok("数据处理完成");
}

3. 关键组件说明

(1) Redis配置

确保Spring Boot项目已集成Redis,配置连接信息:

spring:redis:host: localhostport: 6379password: timeout: 5000

(2) DTO序列化

确保DTO类实现Serializable接口,或使用JSON序列化:

public class DataDTO implements Serializable {private String field1;private int field2;// getters/setters
}

(3) 安全性优化

  • Token生成:使用UUID或JWT保证唯一性和安全性。

  • 数据加密:若DTO包含敏感信息,可在存储到Redis前加密。

以上是正常的在Redis存储临时数据的一个完整过程,属于比较基本的操作,但是倘若我们存储的数据比较大,那么在存储数据到redis的时候就会出现一些内存溢出或超时等异常,所以下面是主要针对这种数据场景的一些处理方案。

4. 处理大数据量的优化【重点】

如果文件解析后的DTO数据量极大(如超过10MB),需优化存储和传输:以下是我总结的一些常用的数据存储方案。

(1) 分片存储

将数据拆分为多个块存入Redis,避免单键过大:

// 存储分片
for (int i = 0; i < parsedData.size(); i += CHUNK_SIZE) {List<DataDTO> chunk = parsedData.subList(i, Math.min(i + CHUNK_SIZE, parsedData.size()));redisTemplate.opsForList().rightPushAll("upload:data:" + token + ":chunks", chunk);
}// 读取分片
List<DataDTO> allData = new ArrayList<>();
while (redisTemplate.opsForList().size(redisKey) > 0) {List<DataDTO> chunk = redisTemplate.opsForList().leftPop(redisKey);allData.addAll(chunk);
}

(2) 压缩数据【推荐】

在存储到Redis前对数据进行压缩(如GZIP),

// 压缩
ByteArrayOutputStream bos = new ByteArrayOutputStream();
GZIPOutputStream gzip = new GZIPOutputStream(bos);
ObjectOutputStream oos = new ObjectOutputStream(gzip);
oos.writeObject(parsedData);
oos.close();
byte[] compressedData = bos.toByteArray();
redisTemplate.opsForValue().set(redisKey, compressedData);// 解压
byte[] compressedData = (byte[]) redisTemplate.opsForValue().get(redisKey);
ByteArrayInputStream bis = new ByteArrayInputStream(compressedData);
GZIPInputStream gzip = new GZIPInputStream(bis);
ObjectInputStream ois = new ObjectInputStream(gzip);
List<DataDTO> parsedData = (List<DataDTO>) ois.readObject();

5. 异常处理

(1) Token过期或无效

在从redis中获取缓存数据的时候,要考虑到Redis中数据是否已经过期等问题,并且针对相应的情况作出返回错误提示,要求用户重新上传文件:

if (parsedData == null) {return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("操作超时或Token无效,请重新上传文件");
}

(2) 数据反序列化失败

在从redis获取到数据json,将其反序列化为具体对象时,如果你序列化和反序列化使用的方式不同,可能会出现反序列化失败的问题,所以针对可能出现的这种情况,一般建议捕获异常并记录日志:

try {List<DataDTO> parsedData = (List<DataDTO>) redisTemplate.opsForValue().get(redisKey);
} catch (SerializationException e) {logger.error("反序列化失败: {}", e.getMessage());return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("数据处理错误");
}

在上面存储数据的时候,如果你的对象嵌套比较复杂,那么还有可能会出现下面的问题,这也是我在存储复杂对象数据到Redis时遇到的一个问题

Java对象存储到Redis报StackOverflowError错误解决

在Java中将对象存储到Redis时遇到StackOverflowError错误,通常是由于对象之间存在循环引用导致序列化时无限递归,以下是逐步解决方案:

1. 确认错误原因

检查异常堆栈跟踪,确认是否在序列化过程中触发StackOverflowError。典型场景:

com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError)

2. 解决循环引用问题

方案一:使用 @JsonIgnore 忽略循环字段

在可能引发循环引用的字段上添加注解,阻止其序列化,不过这种方式要确认你忽略的字段确实是不需要序列化的,否则这个属性值会在序列化后丢失。

public class User {private String name;@JsonIgnore // 忽略此字段的序列化private User friend;// getters/setters
}

方案二:使用 @JsonManagedReference 和 @JsonBackReference【推荐】

通常引起上面问题的主要原因就是数据模型在定义的过程中出现了数据循环递归的情况,导致数据无限的序列化下去,在这里可以通过使用这两个注解来明确父子关系,避免无限递归:

public class Parent {private String name;@JsonManagedReference // 标记为“主”引用private List<Child> children;// getters/setters
}public class Child {private String name;@JsonBackReference // 标记为“反向”引用private Parent parent;// getters/setters
}

方案三:配置 Jackson 忽略循环引用

在 ObjectMapper 中配置,允许忽略循环引用:

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.FAIL_ON_SELF_REFERENCES, false);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

3. 使用 Redis 序列化器避免递归

如果使用 Spring Data Redis,建议更换为 GenericJackson2JsonRedisSerializer,并配置其处理循环引用:

@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(factory);// 使用 Jackson 序列化器ObjectMapper objectMapper = new ObjectMapper();objectMapper.enable(SerializationFeature.INDENT_OUTPUT);objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer(objectMapper);template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(serializer);return template;}
}

4. 优化对象结构

如果无法修改源码,可创建 DTO(数据传输对象),仅序列化必要字段:

public class UserDTO {private String name;// 不包含 friend 字段public UserDTO(User user) {this.name = user.getName();}// getters/setters
}

其他推荐(本地缓存caffeine)

如果你的系统不需要考虑高可用和分布式,那么对比使用Redis缓存来存储临时数据,我更推荐使用本地缓存caffeine来存储,

这种方式不仅不需要将对象进行序列化和反序列化,而且可以有效避免大数据对象存储和获取时存在的性能问题,因为它是完全基于内存来实现的,方便易用。且几乎没有性能损耗。

总结

在通过Redis存储大量数据时,推荐使用压缩和解压缩的形式进行存储。

在存储复杂对象时,建议提前确认对象之间是否存在嵌套引用的情况,如果存在这种情况,建议确认数据模型定义是否合理,如果数据模型定义不合理,建议优先选择优化数据模型,否则建议使用 @JsonManagedReference 和 @JsonBackReference注解来标明主从结构,从而避免对象之间存在循环引用,导致序列化时无限递归的问题。

相关文章:

Redis存储“大数据对象”的常用策略及StackOverflowError错误解决方案

Hi&#xff0c;大家好&#xff0c;我是灰小猿&#xff01; 在一些功能的开发中&#xff0c;我们一般会有一些场景需要将得到的数据先暂时的存储起来&#xff0c;以便后面的接口或业务使用&#xff0c;这种场景我们一般常用的场景就是将数据暂时存储在缓存中&#xff0c;之后再…...

在轨道交通控制系统中如何实现μs级任务同步

轨道交通作为现代城市化进程中的重要支柱,承载着数以亿计的乘客出行需求,同时也是城市经济运行的命脉。无论是地铁、轻轨还是高速铁路,其控制系统的稳定性和可靠性直接关系到运营安全和效率。在这样一个高风险、高复杂度的环境中,任何微小的失误都可能导致灾难性后果。因此…...

嵌入式程序设计英语

实际要求:认识最基本的英文单词即可&#xff0c;(总计几百个) IDE 集成开发环境 fatal error fatal 致命的&#xff0c;error 错误&#xff0c;fatal error 致命的错误 main 主要的 include 包含 io input 输入&#xff0c;output 输出&#xff0c;input output …...

滚轮控制目标臂长度调整相机距离

通过鼠标滚轮来控制摄像机目标臂长度 , 调整相机距离 看图就行,不多说,照着连就完事了...

​‌FireCrawl‌爬虫工具​, Craw4ai

‌FireCrawl‌是一款开源的AI爬虫工具&#xff0c;专门用于Web数据提取&#xff0c;并将其转换为Markdown格式或其他结构化数据。FireCrawl特别适合处理使用JavaScript动态生成的网站&#xff0c;能够自动抓取网站及其所有可访问的子页面内容&#xff0c;并将其转换为适合大语言…...

pyenv库应用入门与Ubuntu端安装实践

pyenv库应用入门与Ubuntu端安装实践 pyenv概述virtualenv、pyvenv、pyenvvirtualenvpyvenvpyenv Ubuntu端安装pyenv实践安装依赖报错解决安装pyenv配置环境变量更换pyenv源地址 pyenv基本用法安装成功服务器部署scrapyd pyenv概述 pyenv 是一个用于管理多个 Python 版本的工具…...

CS5346 - Annotation in Visualization (可视化中的注释)

文章目录 Annotation 的重要性Levels of Annotation &#xff08;注释的层级&#xff09;Headings and IntroductionHeadings&#xff08;标题&#xff09;陈述型&#xff08;Statement&#xff09;&#xff1a;突出结论或有趣发现疑问型&#xff08;Question&#xff09;&…...

如何开发一套场外个股期权交易系统?个股期权交易软件包含:询价,报价,交易,持仓,行权,账户盈亏统计等

一、场外个股期权的定义与特点 场外个股期权&#xff08;Over-the-Counter Equity Option&#xff09;是一种由交易双方私下协商的非标准化金融衍生品合约&#xff0c;以特定个股为标的资产。与交易所上市的标准化期权不同&#xff0c;其合约条款&#xff08;如行权价、到期日…...

高速电路中的电阻、电容的选型及应用

2.1 电阻的应用 2.1.1 与电阻相关的经典案例 如果说芯片是电路的骨架&#xff0c;那么电阻就是在芯片之间起连接作用的关节。电阻的阻值、布放位置等&#xff0c;对设计的成功起着至关重要的作用。 【案例2.1】串联电阻过大&#xff0c;导致板间告警失败 某产品由业务板和主…...

六、adb通过Wifi连接

背景 收集是荣耀X40,数据线原装全新的&#xff0c;USB连上之后&#xff0c;老是断&#xff0c;电脑一直叮咚叮咚的响个不停&#xff0c;试试WIFI 连接是否稳定&#xff0c;需要手机和电脑用相同的WIFI. 连接 1.通过 USB 连接手机和电脑(打开USB调试等这些都略过) adb device…...

Java新手村第二站:泛型、集合与IO流初探

文章目录 Java新手村第二站&#xff1a;泛型、集合与IO流初探泛型包装类集合IO流函数式接口和Lambda表达式 Java新手村第二站&#xff1a;泛型、集合与IO流初探 泛型 泛型的概念与作用&#xff1a; 核心目的&#xff1a;在编译期提供类型安全检查&#xff0c;避免运行时的 Cla…...

AT_abc398_e [ABC398E] Tree Game 题解

题目传送门 题目大意 题目描述 本题是一道交互题&#xff08;你的程序需要通过输入输出与评测系统进行交互&#xff09;。 给定一棵包含 N N N 个顶点的树 G G G&#xff0c;顶点编号为 1 1 1 至 N N N。第 i i i 条边连接顶点 U i U_i Ui​ 和 V i V_i Vi​。 你和…...

CSI-external-provisioner

main() 这段Go代码是一个CSI&#xff08;容器存储接口&#xff09;Provisioner&#xff08;供应器&#xff09;的实现&#xff0c;用于在Kubernetes集群中动态提供持久卷。代码涉及多个组件和步骤&#xff0c;下面是对关键部分的解释&#xff1a; 初始化和配置 命令行标志和…...

android中dp和px的关系

关于android的dp和px的关系是我刚开始学习android的第一个知识点&#xff0c;不知不觉学安卓也有一年了&#xff0c;但是偶然间我发现我理解的dp和px的关系一直是错的&#xff0c;真的是有一点搞笑&#xff0c;今天特意写一篇博客纪念一下这个我理解错一年的知识点。 dp和px之间…...

‌DeepSeek模型在非图形智能体的应用中是否需要GPU

答&#xff1a;不一定 概念 1、是否需要GPU与应用是否图形处理应用无关 2、文本内容智能体大多也需要GPU来提供更好的性能 3、‌DeepSeek模型在非图形智能体的应用中是否需要GPU取决于具体的模型版本和部署环境 不需要GPU的模型版本 ‌DeepSeek-R1-1.5B‌&#xff1a; 这…...

4.14代码随想录第四十三天打卡

图论理论基础 https://www.programmercarl.com/kamacoder/%E5%9B%BE%E8%AE%BA%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html 98. 所有可达路径 (1)题目描述: (2)解题思路: #include <iostream> #include <vector> #include <list> using namespace std;vec…...

【视频目标分割论文集】Efficient Track Anything0000

github 摘要 视频对象分割和追踪任意目标领域出现了强大的工具——分割任意模型 2&#xff08;SAM 2&#xff09;。SAM 2 实现令人印象深刻的视频对象分割性能的关键组成部分包括用于帧特征提取的大型多阶段图像编码器&#xff0c;以及存储过去帧记忆上下文以辅助当前帧分割的…...

码率自适应(ABR)决策的直播场景

直播场景 1. 直播场景的普遍框架与工作原理 主播端&#xff1a;即各类主播&#xff08;游戏、网红歌手、户外达人等&#xff09;&#xff0c;通过手机端或者个人电脑在线直播录制个人活动。 编码服务器&#xff1a;主播端上传视频流以后&#xff0c;编码服务器根据相应的编码转…...

SCP-Firmware安全通告:CVE-2024-11863和CVE-2024-11864

安全之安全(security)博客目录导读 目录 一、概述 二、CVE详情 三、受影响产品 四、修复建议 五、致谢 六、版本历史 一、概述 在SCP固件(SCP-Firmware)中发现两处安全漏洞&#xff0c;可能允许普通世界特权软件&#xff08;normal world privileged software&#xff…...

Redis高频面试题(含答案)

当然可以,Redis 是面试中非常常见的高频考点,尤其在后台开发、分布式系统、缓存设计等方向,面试官常常通过 Redis 来考察你的高并发处理能力、系统设计能力和对缓存一致性理解。 以下是一些典型 Redis 的面试场景题目类型和你可以如何回答的思路: ✅ 一、基础使用类问题 …...

双按键控制LED(中断优先级)

1.启动时&#xff0c;两个LED灯熄灭&#xff0c;1秒钟后&#xff08;定时器实现&#xff09;&#xff0c;LED自动点亮&#xff1b; 2.按键1按下后&#xff0c;通过中断int0把两个LED熄灭5s时间&#xff0c;int0优先级设置为最高&#xff08;优先级必须设置&#xff0c;设置后才…...

(四)机器学习---逻辑回归及其Python实现

之前我们提到了常见的任务和算法&#xff0c;本篇我们使用逻辑回归来进行分类 分类问题回归问题聚类问题各种复杂问题决策树√线性回归√K-means√神经网络√逻辑回归√岭回归密度聚类深度学习√集成学习√Lasso回归谱聚类条件随机场贝叶斯层次聚类隐马尔可夫模型支持向量机高…...

代码随想录第17天:二叉树

一、二叉搜索树的最近公共祖先&#xff08;Leetcode 235&#xff09; 由于是二叉搜索树&#xff0c;节点的值有严格的顺序关系&#xff1a;左子树的节点值都小于父节点&#xff0c;右子树的节点值都大于父节点。利用这一点&#xff0c;可以在树中更高效地找到最低公共祖先。 c…...

超越CUDA:ROCm与oneAPI在异构计算中的性能对比实验(国产GPU生态下的开发路径探索)

一、异构计算生态的竞争格局 当前异构计算领域呈现“一超多强”格局&#xff1a;英伟达凭借‌CUDA生态‌占据90%以上的AI训练市场份额‌&#xff0c;而AMD的‌ROCm‌与英特尔的‌oneAPI‌通过差异化技术路线持续挑战其垄断地位。二者在国产GPU生态建设中展现出独特价值—— R…...

全新电脑如何快速安装nvm,npm,pnpm

以下是全新电脑快速安装 nvm、npm 和 pnpm 的详细步骤&#xff0c;覆盖 Windows/macOS/Linux 系统&#xff1a; 一、安装 nvm&#xff08;Node Version Manager&#xff09; 1. Windows 系统 下载安装包&#xff1a; 访问 nvm-windows 官方仓库&#xff0c;下载 nvm-setup.ex…...

面试篇 - GPT-1(Generative Pre-Training 1)

GPT-1&#xff08;Generative Pre-Training 1&#xff09; ⭐模型结构 Transformer only-decoder&#xff1a;GPT-1模型使用了一个12层的Transformer解码器。具体细节与标准的Transformer相同&#xff0c;但位置编码是可训练的。 注意力机制&#xff1a; 原始Transformer的解…...

测试用例如何编写

综合起来&#xff0c;做测试用例时&#xff0c;需要考虑两个方面&#xff08;主要配合接口测试&#xff09; ①页面上显示的数据是从哪里来的&#xff0c;是否有全部显示 -- 简单来说就是数据效验②页面上显示的数据是否有交互/依赖&#xff08;操作的先后顺序会影响页面显示的…...

读者、写者问题优化

#include <stdio.h> #include <time.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> #include <semaphore.h> #define NUM_READERS 5 #define NUM_WRITERS 5 // 定义信号量和全局变量 sem_t sdata, srcount; int rea…...

AI推理强,思维模型也有功劳【60】启发式偏差思维

giszz的理解&#xff1a;你以为你以为的&#xff0c;就是对的吗&#xff1f;以谨慎的心态去面对不确定&#xff0c;保持空杯心态&#xff0c;不要因走捷径而出现偏差。 一、定义 启发式偏差思维模型是指人们在面对复杂问题或不确定情境时&#xff0c;倾向于使用启发式&#xf…...

【从零实现高并发内存池】内存池整体框架设计 及 thread cache实现

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;博客仓库&#xff1a;https://gitee.com/JohnKingW/linux_test/tree/master/lesson &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &…...