【实战解决方案】Spring Boot+Redisson构建高并发Excel导出服务,彻底解决系统阻塞难题
【实战解决方案】Spring Boot+Redisson构建高并发Excel导出服务,彻底解决系统阻塞难题
一、问题背景:痛苦的系统卡顿经历
作为电商后台开发者,我们经常遇到这样的场景:运营人员在后台点击"导出订单数据"后,整个系统变得异常卡顿,其他操作全部陷入漫长的等待。特别是在促销活动后,当需要导出10万+条订单数据时,系统几乎瘫痪。
问题症状:
- 导出期间CPU占用率飙升至90%+
- 前端界面无响应
- 数据库查询延迟飙升
- 频繁Full GC导致服务暂停
现有技术栈:
- 后端:Spring Boot 2.7 + Redisson 3.17 + MyBatis
- 存储:MySQL 8.0 + 阿里云OSS
- 前端:Vue 3 + Element Plus
二、问题根源分析
通过Arthas和SkyWalking分析,我们发现核心问题在于:
- 同步阻塞:导出操作与业务接口共用线程池
- 内存爆炸:一次性加载全部数据到内存
- IO瓶颈:大文件生成时磁盘IO饱和
- 无流控机制:可无限制触发导出
三、解决方案:四步构建高性能导出服务
3.1 整体架构设计
基于现有技术栈的优化方案:
3.2 关键技术选型
组件 | 职责 | 技术实现 |
---|---|---|
任务调度 | 分布式协调 | Redisson Queue |
数据处理 | 流式导出 | MyBatis游标 + SXSSFWorkbook |
文件存储 | 海量存储 | 阿里云OSS分片上传 |
状态管理 | 任务跟踪 | MySQL + Redisson Topic |
四、核心代码实现
4.1 任务提交接口
@RestController
@RequestMapping("/api/export")
@RequiredArgsConstructor
public class ExportController {private final RedissonClient redisson;private final ExportTaskMapper taskMapper;@PostMappingpublic Result<String> submitExport(@Valid @RequestBody ExportRequest request) {// 1. 创建任务记录ExportTask task = new ExportTask();task.setTaskId(UUID.randomUUID().toString());task.setStatus(0); // 0-待处理task.setCreateTime(LocalDateTime.now());taskMapper.insert(task);// 2. 发布到Redis队列RBlockingDeque<String> queue = redisson.getBlockingDeque("export:tasks");queue.offer(task.getTaskId());return Result.success(task.getTaskId());}@GetMapping("/progress/{taskId}")public Result<ExportProgress> getProgress(@PathVariable String taskId) {ExportTask task = taskMapper.selectById(taskId);return Result.success(new ExportProgress(task.getStatus(),task.getProgress(),task.getOssUrl()));}
}
4.2 MyBatis游标查询
@Mapper
public interface OrderMapper {@Select("SELECT * FROM orders WHERE #{criteria}")@Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = 1000)Cursor<Order> selectByCursor(@Param("criteria") String criteria);
}// 使用示例
try (Cursor<Order> cursor = orderMapper.selectByCursor(criteria)) {cursor.forEach(order -> {// 处理每条数据});
}
4.3 分布式Worker实现
@Component
@RequiredArgsConstructor
public class ExportWorker {private final RedissonClient redisson;private final OrderMapper orderMapper;private final ExportTaskMapper taskMapper;private final OSS ossClient;@PostConstructpublic void startWorkers() {// 启动3个Worker线程IntStream.range(0, 3).forEach(i -> {new Thread(this::processTask, "export-worker-" + i).start();});}private void processTask() {RBlockingDeque<String> queue = redisson.getBlockingDeque("export:tasks");while (true) {String taskId = null;try {// 1. 获取任务taskId = queue.takeFirst();ExportTask task = taskMapper.selectById(taskId);// 2. 更新状态task.setStatus(1); // 处理中taskMapper.updateById(task);// 3. 创建OSS分片上传String objectName = "export/" + taskId + ".xlsx";InitiateMultipartUploadResult uploadResult = ossClient.initiateMultipartUpload(new InitiateMultipartUploadRequest(bucketName, objectName));// 4. 流式处理processData(taskId, objectName, uploadResult.getUploadId());} catch (Exception e) {if (taskId != null) {markTaskFailed(taskId, e);}log.error("Export failed", e);}}}private void processData(String taskId, String objectName, String uploadId) throws Exception {List<PartETag> partETags = new ArrayList<>();int partNumber = 1;int rowCount = 0;// 使用SXSSF实现流式Excel生成try (SXSSFWorkbook workbook = new SXSSFWorkbook(100)) {Sheet sheet = workbook.createSheet("订单数据");// 游标查询数据try (Cursor<Order> cursor = orderMapper.selectByCursor(buildCriteria())) {for (Order order : cursor) {// 写入行数据Row row = sheet.createRow(rowCount++);// ...填充单元格数据// 每1000行上传一个分片if (rowCount % 1000 == 0) {partETags.add(uploadPart(workbook, partNumber++, uploadId, objectName));workbook.dispose(); // 清理临时文件}}}// 上传最后的分片if (rowCount % 1000 != 0) {partETags.add(uploadPart(workbook, partNumber, uploadId, objectName));}}// 完成分片上传CompleteMultipartUploadRequest completeRequest = new CompleteMultipartUploadRequest(bucketName, objectName, uploadId, partETags);ossClient.completeMultipartUpload(completeRequest);// 更新任务状态updateTaskSuccess(taskId, objectName, rowCount);}
}
五、Redisson关键配置
application.yml配置
spring:redis:host: redis-hostport: 6379password: ${REDIS_PASSWORD}redisson:config: |singleServerConfig:idleConnectionTimeout: 10000connectTimeout: 10000timeout: 3000retryAttempts: 3retryInterval: 1500connectionPoolSize: 16connectionMinimumIdleSize: 4database: 0
六、性能优化成果
指标 | 优化前 | 优化后 |
---|---|---|
10万数据导出时间 | 180秒 | 45秒 |
内存占用峰值 | 2GB+ | 200MB |
系统影响 | 整个系统卡顿 | 零影响 |
最大并发导出 | 1个 | 10+个 |
CPU占用峰值 | 90%+ | <30% |
七、部署建议
1. 容器化部署示例
FROM openjdk:11-jre
WORKDIR /app
COPY target/export-service.jar .
CMD ["java", "-Xmx512m", "-Xms128m", "-jar", "export-service.jar"]
2. 健康检查配置
# Spring Boot Actuator配置
management:endpoint:health:show-details: alwaysendpoints:web:exposure:include: health,metrics
八、总结与展望
通过本方案我们实现了:
- 系统解耦:导出与业务分离
- 资源隔离:专用Worker处理
- 弹性扩展:基于Redisson的分布式能力
- 内存优化:流式处理+分片上传
未来优化方向:
- 增加导出任务优先级机制
- 实现动态Worker扩缩容
- 添加导出失败自动重试
- 完善导出监控告警体系
希望这篇实战经验能帮助遇到类似问题的开发者!如果对实现细节有疑问,欢迎在评论区交流讨论。
相关文章:
【实战解决方案】Spring Boot+Redisson构建高并发Excel导出服务,彻底解决系统阻塞难题
【实战解决方案】Spring BootRedisson构建高并发Excel导出服务,彻底解决系统阻塞难题 一、问题背景:痛苦的系统卡顿经历 作为电商后台开发者,我们经常遇到这样的场景:运营人员在后台点击"导出订单数据"后,…...

Delphi 12.3调用Chrome/edge内核实现DEMO源码
DELPHI使用调用Chrome/Edge内核浏览器,虽然旧的WebBrowser也还可以用,但大势所趋,新版的已经不需要使用第三方的组件了,算是全内置的开发了,不废话 Unit1 源码 Form 源码 unit Unit1;interfaceusesWinapi.Windows, W…...

GitDiagram - GitHub 仓库可视化工具
GitDiagram - GitHub 仓库可视化工具 项目链接:https://github.com/ahmedkhaleel2004/gitdiagram 将任何 GitHub 仓库转换为交互式架构图,只需替换 URL 中的 hub 为 diagram。 ✨ 核心功能 即时可视化:将代码库结构转换为系统设计/架构图…...

【Linux】基于虚拟机实现网络的管理
通过学习我们需要掌握:IP 的配置、子网掩码、网关、DNS 服务器】 一、配置虚拟机的IP地址 1. 查看虚拟机 IP 地址(可以看到三个地址) ip a(即ip address show) 其中可以看到: Linux系统识别的以太网接口…...

QT 使用QPdfWriter和QPainter绘制PDF文件
QT如何生产pdf文件,网上有许多文章介绍,我也是看了网上的文章,看他们的代码,自己琢磨琢磨,才有了本编博客; 其他什么就不详细说了,本篇博客介绍的QPdfWriter和QPainter绘制PDF文件;…...
英迈国际Ingram Micro EDI需求分析
Ingram Micro(英迈国际)成立于1979年,是全球领先的技术和供应链服务提供商,总部位于美国加州尔湾。公司致力于连接全球的技术制造商与渠道合作伙伴,业务涵盖IT分销、云服务、物流和供应链优化等多个领域。Ingram Micro…...

linux - 权限的概念
目录 用户权限 超级用户与普通用户的区别 超级用户(root): 普通用户: 切换用户身份 使用sudo执行高权限命令 用户管理 用户组管理 文件权限 文件访问者类别 基本权限 权限表示方法 权限修改 chmod chown chgrp u…...
函数的定义与调用 -《Go语言实战指南》
函数是 Go 编程的基本单元。Go 支持普通函数、匿名函数、高阶函数(函数作为参数或返回值)以及多返回值机制。 一、函数的定义格式 func 函数名(参数列表) 返回值列表 {// 函数体 } 示例: func add(a int, b int) int {return a b } 说明&…...
理解 Token 索引 vs 字符位置
以下是对“理解 Token 索引与字符位置的区别”的内容整理,条理清晰,结构完整,保持技术细节,方便阅读,无多余解释: 🔍 理解 Token 索引 vs 字符位置 文本分块方法中返回的索引是 token 索引&…...

【Vue】CSS3实现关键帧动画
关键帧动画 两个重点keyframesanimation子属性 实现案例效果展示: 两个重点 keyframes 和 animation 作用:通过定义关键帧(keyframes)和动画(animation)规则,实现复杂的关键帧动画。 keyframes 定义动画的关键帧序列…...

AD 多层线路及装配图PDF的输出
装配图的输出: 1.点开‘智能PDF’ 2. 设置显示顶层: 设置显示底层: 多层线路的输出 同样使用‘智能PDF’...

MultiTTS 1.7.6 | 最强离线语音引擎,提供多音色无障碍朗读功能,附带语音包
MultiTTS是一款免费且支持离线使用的文本转语音(TTS)工具,旨在为用户提供丰富的语音包选项,实现多音色无障碍朗读功能。这款应用程序特别适合用于阅读软件中的离线听书体验,提供了多样化的语音选择,使得听书…...

基于自校准分数的扩散模型在并行磁共振成像中联合进行线圈灵敏度校正和运动校正|文献速递-深度学习医疗AI最新文献
Title 题目 Joint coil sensitivity and motion correction in parallel MRI with aself-calibrating score-based diffusion model 基于自校准分数的扩散模型在并行磁共振成像中联合进行线圈灵敏度校正和运动校正 01 文献速递介绍 磁共振成像(MRI)…...

OCR发票识别API实现
OCR发票识别API实现 1. 阿里云OCR发票识别2. Tesseract OCR3. 利用java调用大模型进行识别4. 飞桨PaddleOCR 1. 阿里云OCR发票识别 阿里云OCR发票识别 示例: 接口:https://dgfp.market.alicloudapi.com/ocrservice/invoice 参数:{"img&…...

实战案例:采集 51job 企业招聘信息
本文将带你从零开始,借助 Feapder 快速搭建一个企业级招聘信息数据管道。在“基础概念”部分,我们先了解什么是数据管道和 Feapder;“生动比喻”用日常场景帮助你快速理解爬虫组件;“技术场景”介绍本项目中如何使用代理等采集策略…...

从AlphaGo到ChatGPT:AI技术如何一步步改变世界?
从AlphaGo到ChatGPT:AI技术如何一步步改变世界? 这里给大家分享一个人工智能学习网站。点击跳转到网站。 https://www.captainbed.cn/ccc 前言 在科技发展的历史长河中,人工智能(AI)技术无疑是最为璀璨的明珠之一。从…...
推荐6大wordpress模板资源网站
1. 模板之家 模板之家是一个提供丰富网站模板资源的平台。它涵盖了多种类型的模板,包括企业官网、个人博客、电商网站等,能够满足不同用户对于网站搭建的需求。其模板设计精美,功能多样,且注重用户体验,方便用户快速搭…...

AI 编程革命:腾讯云 CodeBuddy 如何重塑开发效率?
引言 在传统开发流程中,开发者常需依赖 SDK 文档或反复调试来获取云资源信息。而随着 AI 技术爆发式发展,腾讯云推出的 CodeBuddy 正以对话式编程颠覆这一模式 —— 只需自然语言描述需求,即可直接生成可执行代码。作为腾讯混元大模型与 Dee…...

星海智算云平台部署GPT-SoVITS模型教程
背景 随着 GPT-SoVITS 在 AI 语音合成领域的广泛应用,越来越多的个人和团队开始关注这项前沿技术。你是否也在思考,如何快速、高效地部署并体验这款强大的声音克隆模型?遗憾的是,许多本地部署方案不仅配置复杂,而且对…...

15:00开始面试,15:06就出来了,问的问题有点变态。。。
从小厂出来,没想到在另一家公司又寄了。 到这家公司开始上班,加班是每天必不可少的,看在钱给的比较多的份上,就不太计较了。没想到4月一纸通知,所有人不准加班,加班费不仅没有了,薪资还要降40%…...
python 的 uv、pip 和 conda 对比和技术选型
你好,我是 shengjk1,多年大厂经验,努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注!你会有如下收益: 了解大厂经验拥有和大厂相匹配的技术等 希望看什么,评论或者私信告诉我! 文章目录 一…...

20250515通过以太网让VLC拉取视熙科技的机芯的rtsp视频流的步骤
20250515通过以太网让VLC拉取视熙科技的机芯的rtsp视频流的步骤 2025/5/15 20:26 缘起:荣品的PRO-RK3566适配视熙科技 的4800W的机芯。 1080p出图预览的时候没图了。 通过105的机芯出图确认 荣品的PRO-RK3566 的硬件正常。 然后要确认 视熙科技 的4800W的机芯是否出…...
GPU异步执行漏洞攻防实战:从CUDA Stream竞争到安全编程规范
点击 “AladdinEdu,同学们用得起的【H卡】算力平台”,H卡级别算力,按量计费,灵活弹性,顶级配置,学生专属优惠。 引言 在高校实验室的GPU加速计算研究中,多卡并行编程已成为提升深度学习训练效…...

UE5.3 C++ 房屋管理系统(二)
三.当房屋生成成功,我们就需要把TMap里的数据存到数据库里。不然一点停止运行,就会所以数据都不见了。这里使用DataTable来存储。 1.DataTable是UE常用的表,虽然不是专门用来存档的,但也可以这么用。 DataTable表,实…...

VSCode1.101.0便携版|中英文|编辑器|安装教程
软件介绍 Visual Studio Code是微软推出的一个强大的代码编辑器,功能强大,操作简单便捷,还有着良好的用户界面,设计得很人性化,旨在为所有开发者提供一款专注于代码本身的免费的编辑器。 软件安装 1、 下载安装包…...

Linux系统发布.net core程序
前端 前端用的Vue3,发布的话需要Nginx下载安装Nginx 麒麟:这里我麒麟用的是桌面版,我直接把操作流程写在下面,写的比较简单,具体的可以具体搜这一块内容学习一下。打包vue程序,通过MobaXterm将打包后的程序…...
当需要在一个方法中清除多个缓存时,@CacheEvict注解能否实现这个需求
想清除Redis中的多个缓存数据,如何实现? CacheEvict清除一个缓存,但现在想知道如何处理多个缓存的情况。场景:可能有一个更新用户信息的方法,这个方法执行后,不仅需要清除某个特定的用户缓存,还…...

极新携手火山引擎,共探AI时代生态共建的破局点与增长引擎
在生成式AI与行业大模型的双重驱动下,人工智能正以前所未有的速度重构互联网产业生态。从内容创作、用户交互到商业决策,AI技术渗透至产品研发、运营的全链条,推动效率跃升与创新模式变革。然而,面对AI技术迭代的爆发期࿰…...
Score-CAM:卷积神经网络的评分加权视觉解释
摘要 最近,越来越多的关注被引向卷积神经网络的内部机制,以及网络为何会做出特定决策。本文中,我们开发了一种基于类别激活映射的新颖事后可视化解释方法,称为Score-CAM。与以往基于类别激活映射的方法不同,Score-CAM通过前向传递得分获取每个激活图的权重,从而摆脱了对…...
Python刷题练习
文章目录 1.寻找相同字串2.密钥格式化3.五键键盘的输出4.单词重量5.输出指定字母在字符串的中的索引6.污染水域7.九宫格按键输入8.任务最优调度9.高效的任务规划 1.寻找相同字串 题目描述: 给你两个字符串t和p,要求从t中找到一个和p相同的连续子串,并输…...