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注解来标明主从结构,从而避免对象之间存在循环引用,导致序列化时无限递归的问题。

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

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

相关文章

【Python】读取xyz坐标文件输出csv文件

Python读取xyz坐标文件输出csv文件 import sys import numpy as np import pandas as pd from tqdm import tqdm import cv2 import argparsedef read_xyz(file_path):with open(file_path, "r") as f: # 打开文件data f.readlines() # 读取文件datas []for …

leetcode 139. Word Break

这道题用动态规划解决。 class Solution { public:bool wordBreak(string s, vector<string>& wordDict) {unordered_set<string> wordSet;for(string& word:wordDict){wordSet.insert(word);}int s_len s.size();//s的下标从1开始起算&#xff0c;dp[j]…

驱动开发硬核特训 · Day 11(下篇):从 virtio_blk 看虚拟总线驱动模型的真实落地

&#x1f50d; B站相应的视屏教程&#xff1a; &#x1f4cc; 内核&#xff1a;博文视频 - 总线驱动模型实战全解析 敬请关注&#xff0c;记得标为原始粉丝。 &#x1f527; 在上篇中&#xff0c;我们已经从理论视角分析了“虚拟总线驱动模型”在 Linux 驱动体系中的独特定位。…

音视频转换器 AV 接口静电保护方案

方案简介 音视频转换器是将音视频&#xff08;AV&#xff09;信号转换成其他格式或信号类型的设备或软件。 它能够实现大多数视频、音频以及图像格式之间的转换&#xff0c;包括但不限于 RMVB、AVI、 MP4、MOV 等常见格式&#xff0c;同时也支持将不同采样率、位深度、声道数…

AI agents系列之全从零开始构建

在我们上一篇博客文章中&#xff0c;我们全面介绍了智能代理&#xff0c;讨论了它们的特性、组成部分、演变过程、面临的挑战以及未来的可能性。 这篇文章&#xff0c;咱们就来聊聊怎么用 Python 从零开始构建一个智能代理。这个智能代理能够根据用户输入做出决策&#xff0c;…

【Python爬虫】详细工作流程以及组成部分

目录 一、Python爬虫的详细工作流程 确定起始网页 发送 HTTP 请求 解析 HTML 处理数据 跟踪链接 递归抓取 存储数据 二、Python爬虫的组成部分 请求模块 解析模块 数据处理模块 存储模块 调度模块 反爬虫处理模块 一、Python爬虫的详细工作流程 在进行网络爬虫工…

Kotlin 集合过滤全指南:all、any、filter 及高级用法

在 Kotlin 中&#xff0c;集合过滤是数据处理的核心操作之一。无论是简单的条件筛选&#xff0c;还是复杂的多条件组合&#xff0c;Kotlin 都提供了丰富的 API。本文将详细介绍 filter、all、any、none 等操作符的用法&#xff0c;并展示如何在实际开发中灵活运用它们。 1. 基础…

爬虫:一文掌握 curl-cffi 的详细使用(支持 TLS/JA3 指纹仿真的 cURL 库)

更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、curl-cffi 概述1.1 curl-cffi介绍1.2 主要特性1.3 适用场景1.4 使用 curl-cffi 的注意事项1.5 与 requests 和 pycurl 对比1.6 curl-cffi 的安装二、基本使用2.1 同步请求2.2 异步请求三、高级功能3.1 模拟浏览器指…

AllData数据中台升级发布 | 支持K8S数据平台2.0版本

&#x1f525;&#x1f525; AllData大数据产品是可定义数据中台&#xff0c;以数据平台为底座&#xff0c;以数据中台为桥梁&#xff0c;以机器学习平台为中层框架&#xff0c;以大模型应用为上游产品&#xff0c;提供全链路数字化解决方案。 ✨杭州奥零数据科技官网&#xf…

dnf install openssl失败的原因和解决办法

网上有很多编译OpenSSL源码(3.x版本)为RPM包的文章&#xff0c;这些文章在安装RPM包时都是执行rpm -ivh openssl-xxx.rpm --nodeps --force 这个命令能在缺少依赖包的情况下能强行执行安装 其实根据Centos的文档&#xff0c;安装RPM包一般是执行yum install或dnf install。后者…

从入门到进阶:React 图片轮播 Carousel 的奇妙世界!

全文目录&#xff1a; 开篇语&#x1f590; 前言✨ 目录&#x1f3af; 什么是图片轮播组件&#xff1f;&#x1f528; 初识 React 中的轮播实现示例代码分析 &#x1f4e6; 基于第三方库快速实现轮播示例&#xff1a;用 react-slick优势局限性 &#x1f6e0;️ 自己动手实现一个…

2025第十六届蓝桥杯PythonB组部分题解

一、攻击次数 题目描述 小蓝操控三个英雄攻击敌人&#xff0c;敌人初始血量2025&#xff1a; 第一个英雄每回合固定攻击5点第二个英雄奇数回合攻击15点&#xff0c;偶数回合攻击2点第三个英雄根据回合数除以3的余数攻击&#xff1a;余1攻2点&#xff0c;余2攻10点&#xff0…

新手宝塔部署thinkphp一步到位

目录 一、下载对应配置 二、加载数据库 三、添加FTP​ 四、上传项目到宝塔​ 五、添加站点​ 六、配置伪静态 七、其他配置 开启监控 八、常见错误 一、打开宝塔页面&#xff0c;下载对应配置。 二、加载数据库 从本地导入数据库文件 三、添加FTP 四、上传项目到宝塔…

2025年,HarmonyOS认证学习及考试

HarmonyOS应用开发者认证考试 基础认证 通过系统化的课程学习&#xff0c;熟练掌握 DevEco Studio&#xff0c;ArkTS&#xff0c;ArkUI&#xff0c;预览器&#xff0c;模拟器&#xff0c;SDK 等 HarmonyOS 应用开发的关键概念&#xff0c;具备基础的应用开发能力。 高级认证…

3-1 Git分布式版本控制特性探讨

Git 的分布式版本控制特性是其核心优势之一,它使 Git 在版本管理方面具有高度的灵活性、可靠性和高效性。以下从多个方面来理解这一特性: 分布式存储 在 Git 中,每个开发者的本地机器上都拥有完整的版本库,包含了项目的所有历史记录和元数据。这与集中式版本控制系统(如…

flutter 桌面应用之右键菜单

​在 Flutter 桌面应用开发中&#xff0c;context_menu 和 contextual_menu 是两款常用的右键菜单插件&#xff0c;各有特色。以下是对它们的对比分析&#xff1a;​ context_menu 集成方式&#xff1a;​通过 ContextMenuArea 组件包裹目标组件&#xff0c;定义菜单项。​掘金…

Tips:用proxy解决前后端分离项目中的跨域问题

在前后端分离项目中&#xff0c;"跨域问题"是浏览器基于同源策略&#xff08;Same-Origin Policy&#xff09;对跨域请求的安全限制。当你的前端&#xff08;如运行在 http://localhost:3000 &#xff09;和后端&#xff08;如运行在 http://localhost:8080 &#…

基于 Qt 的图片处理工具开发(一):拖拽加载与基础图像处理功能实现

一、引言 在桌面应用开发中&#xff0c;图片处理工具的核心挑战在于用户交互的流畅性和异常处理的健壮性。本文以 Qt为框架&#xff0c;深度解析如何实现一个支持拖拽加载、亮度调节、角度旋转的图片处理工具。通过严谨的文件格式校验、分层的架构设计和用户友好的交互逻辑&am…

设计模式:依赖倒转原则 - 依赖抽象,解耦具体实现

一、为什么用依赖倒转原则&#xff1f; 在软件开发中&#xff0c;类与类之间的依赖关系是架构设计中的关键。如果依赖过于紧密&#xff0c;系统的扩展性和维护性将受到限制。为了应对这一挑战&#xff0c;依赖倒转原则&#xff08;Dependency Inversion Principle&#xff0c;…

vue+d3js+fastapi实现天气柱状图折线图饼图

说明&#xff1a; vued3jsfastapi实现天气柱状图折线图饼图 效果图&#xff1a; step0:postman 1. 生成天气数据&#xff08;POST请求&#xff09;&#xff1a;URL: http://localhost:8000/generate-data/?year2024&month3&seed42 方法: POST Headers:Content-Type:…