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.pswp.cn/news/901383.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/901383.shtml
英文地址,請注明出處:http://en.pswp.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:…