多人同时导出 Excel 干崩服务器?我们来实现一个排队导出功能!

news2024/11/20 16:32:10

考虑到数据库数据日渐增多,导出会有全量数据的导出,多人同时导出可以会对服务性能造成影响,导出涉及到mysql查询的io操作,还涉及文件输入、输出流的io操作,所以对服务器的性能会影响的比较大;

结合以上原因,对导出操作进行排队;

刚开始拿到这个需求,第一时间想到就是需要维护一个FIFO先进先出的队列,给定队列一个固定size,在队列里面的人进行排队进行数据导出,导出完成后立马出队列,下一个排队的人进行操作;

还考虑到异步,可能还需要建个文件导出表,主要记录文件的导出情况,文件的存放地址,用户根据文件列表情况下载导出文件。

业务关系定义

分别是用户、导出队列、导出执行方法

  • ExportQueue: 维护一条定长队列,可以获取队列里前后排队的用户,提供查询,队列如果已经满了,其余的人需要进行等待

  • User信息: 排队执行导出方法对应用户;

  • Export类: 定义导出方法,异步执行,用户可以通过导出页面查看、下载,导出的文件;

图片

具体代码实现

ExportQueue队列
package com.example.system.config;

import com.example.system.api.domain.ExportUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.LinkedList;

@Slf4j
@Component
public class ExportQueue {


    private final int MAX_CAPACITY = 10; // 队列最大容量
    private LinkedList<ExportUser> queue; // 用户队列

    public ExportQueue(LinkedList<ExportUser> queue) {
        this.queue = new LinkedList<>();
    }

    /**
     * 排队队列添加
     * @param sysUser
     */
    public synchronized LinkedList<ExportUser> add(ExportUser sysUser) {
        while (queue.size() >= MAX_CAPACITY) {
            try {
                log.info("当前排队人已满,请等待");
                wait();
            } catch (InterruptedException e) {
                e.getMessage();
            }
        }
        queue.add(sysUser);
        log.info("目前导出队列排队人数:" + queue.size());
        notifyAll();
        return queue;
    }


    /**
     * 获取排队队列下一个人
     * @return
     */
    public synchronized ExportUser getNextSysUser() {
        while (queue.isEmpty()) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        ExportUser sysUser = queue.remove();
        notifyAll(); //唤醒
        return sysUser;
    }
}
AbstractExport导出类

引入EasyExcel百万级别的导出功能

package com.example.system.config;


import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.PageUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.example.system.api.domain.ExportUser;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.List;

@Slf4j
public abstract class AbstractExport<T, K> {


    public abstract void export(ExportUser sysUser) throws InterruptedException;

    /**
     * 导出
     *
     * @param response 输出流
     * @param pageSize 每页大小
     * @param t        导出条件
     * @param k        Excel内容实体类
     * @param fileName 文件名称
     */
    public void export(HttpServletResponse response, int pageSize, T t, Class<K> k, String fileName) throws Exception {
        ExcelWriter writer = null;
        try {
            writer = getExcelWriter(response, fileName);
            //查询导出总条数
            int total = this.countExport(t);
            //页数
            int loopCount = PageUtil.totalPage(total, pageSize);
            BeanUtil.setProperty(t, "pageSize", pageSize);
            for (int i = 0; i < loopCount; i++) {
                //开始页
                BeanUtil.setProperty(t, "pageNum", PageUtil.getStart(i + 1, pageSize));
                //获取Excel导出信息
                List<K> kList = this.getExportDetail(t);
                WriteSheet writeSheet = EasyExcel.writerSheet(fileName).head(k).build();
                writer.write(kList, writeSheet);
            }
        } catch (Exception e) {
            String msg = "导出" + fileName + "异常";
            log.error(msg, e);
            throw new Exception(msg + e);
        } finally {
            if (writer != null) {
                writer.finish();
            }
        }
    }

    public com.alibaba.excel.ExcelWriter getExcelWriter(HttpServletResponse response, String fileName) throws IOException {
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");
        // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
        String fileNameUtf = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileNameUtf + ".xlsx");
        return EasyExcel.write(response.getOutputStream()).build();
    }


    /**
     * (模版导出)
     *
     * @param t
     * @param fileName
     * @param response
     */
    public abstract void complexFillWithTable(T t, String fileName, HttpServletResponse response);

    /**
     * 查询导出总条数
     *
     * @param t
     * @return
     */
    public abstract int countExport(T t);

    /**
     * 查询导出数据
     *
     * @param t
     * @return
     */
    public abstract List<K> getExportDetail(T t);
}
ExportImpl导出实现方法
package com.example.system.service.impl;

import com.alibaba.excel.ExcelWriter;
import com.example.system.api.domain.ExportUser;
import com.example.system.config.AbstractExport;
import com.example.system.config.ExportQueue;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

@Service
@Slf4j
public class ExportImpl extends AbstractExport {

    @Autowired
    private ExportQueue exportQueue;


    @Override
    public void export(ExportUser sysUser) throws InterruptedException {

        //导出
        log.info("导出文件方法执行~~~~~~~~~");
//        export(response,pageSize,t,k,fileName);
        LinkedList<ExportUser> queue = exportQueue.add(sysUser);
        log.info("导出队列:" + queue);
        //休眠时间稍微设置大点,模拟导出处理时间
        Thread.sleep(20000);
        //导出成功后移除当前导出用户
        ExportUser nextSysUser = exportQueue.getNextSysUser();
        log.info("移除后获取下一个排队的用户: " + nextSysUser.getUserName());

    }


    @Override
    public void export(HttpServletResponse response, int pageSize, Object o, Class k, String fileName) throws Exception {
        super.export(response, pageSize, o, k, fileName);
    }

    @Override
    public ExcelWriter getExcelWriter(HttpServletResponse response, String fileName) throws IOException {
        return super.getExcelWriter(response, fileName);
    }

    @Override
    public void complexFillWithTable(Object o, String fileName, HttpServletResponse response) {

    }

    @Override
    public int countExport(Object o) {
        return 0;
    }

    @Override
    public List getExportDetail(Object o) {
        return null;
    }
}
测试controller
package com.example.system.controller;

import com.example.system.api.domain.ExportUser;
import com.example.system.api.domain.SysUser;
import com.example.system.service.impl.ExportImpl;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/export")
@Slf4j
public class ExportController {

    @Autowired
    private ExportImpl export;


    @PostMapping("/exportFile")
    public void exportFile() {
            new Thread(new Runnable() {
                @SneakyThrows
                @Override
                public void run() {
                    Thread thread1 = Thread.currentThread();
                    ExportUser sysUser =new ExportUser();
                    sysUser.setUserName(thread1.getName());

                    export.export(sysUser);
                }
            }).start();
        }
}
测试结果

通过请求测试方法,限制了我们导出队列最大限制10次,队列场长度超过10次则无法进行继续提交;

图片

第一次请求和第二次请求,间隔10秒,第一个用户导出完成后出列,下一个排队用户在队列首位,在进行导出请求排在上一个用户后面;

图片

总结

⚠️其余的还未实现,导出文件的表的设计、oss文件上传、用户导出文件下载,还有高并发的场景下会不会出现什么问题,这些都还没有太考虑进去;

实现的方式应该挺多的,Redis的队列应该也是可以的,这里仅仅提供一个实现思路;

最后说一句(求关注!别白嫖!)

如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、转发、在看。

关注公众号:woniuxgg,在公众号中回复:笔记  就可以获得蜗牛为你精心准备的java实战语雀笔记,回复面试、开发手册、有超赞的粉丝福利!

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

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

相关文章

李沐动手学习深度学习——3.2练习

以下是个人理解&#xff0c;希望进行讨论求解。 练习 1. 如果我们将权重初始化为零&#xff0c;会发生什么。算法仍然有效吗&#xff1f; 根据SGD算法公式如上&#xff0c;第一次迭代的值可知w只与b相关&#xff0c;而对于b的迭代更新&#xff0c;只是与b的初始值相关&#x…

什么是物联网?

今天这篇文章写的相关内容就是带领大家了解什么是物联网&#xff0c;之前写的文章大多都是一些物联网的未来&#xff0c;行业的解决方案等&#xff1b;话不多说开始进入正题吧! 物联网(IoT)是一个包罗万象的术语&#xff0c;指的是越来越多的电子产品&#xff0c;它们不是传统的…

【2022 J1】乘方

本主又双叒叕来更新了&#xff0c;一圈三连不用说了吧&#x1f601; 本题是J组第二轮的题&#xff1a; 题目描述 小文同学刚刚接触了信息学竞赛&#xff0c;有一天她遇到了这样一个题&#xff1a;给定正整数 a 和 b&#xff0c;求 &#xfffd;&#xfffd;ab 的值是多少。 …

Java优先级队列--堆

目录 1. 优先级队列 1.1 概念 2.优先级队列的模拟实现 2.1 堆的概念 2.2 堆的存储方式 2.3 堆的创建 2.3.1 堆向下调整 2.3.2 堆的创建 2.3.3 建堆的时间复杂度 2.4 堆的插入与删除 2.4.1 堆的插入 2.4.2 堆的删除 2.5 用堆模拟实现优先级队列 3.常用接口介绍 3…

Windows下使用C++调用海康威视SDK获取实时视频流进行检测

目录 准备海康威视的SDK官网下载下载后解压 Vs 2022创建项目创建32位的环境 将相关文件复制到工程目录下工程配置海康威视SDK配置包含目录配置库目录将dll文件添加到环境中在附加依赖项添加如下内容 工程配置OpenCV配置工程配置包含目录配置库目录 测试 准备海康威视的SDK 官网…

深入理解Linux线程(LWP):概念、结构与实现机制(1)

&#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;会いたい—Naomile 1:12━━━━━━️&#x1f49f;──────── 4:59 &#x1f504; ◀️ ⏸ ▶️ ☰ &a…

JavaScript | 拼客JavaScript的P211-循环精灵图 精灵图及个人手敲代码分享

拼客JavaScript的P211-循环精灵图 JavaScript前端课程-dom-bom-js-es6新语法-jQuery-数据可视化echarts黑马pink老师前端入门基础视频教程(500多集)持续 视频中的拼客老师使用的是淘宝首页的精灵图资源24-595图片 精灵图24-595 我还找到另一张大一点的精灵图&#xff0c;内容…

【力扣 - 杨辉三角】

题目描述 给定一个非负整数 numRows&#xff0c;生成「杨辉三角」的前 numRows 行。 示例 1: 输入: numRows 5 输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]] 示例 2: 输入: numRows 1 输出: [[1]] 提示: 1 < numRows < 30 方法一&#xff1a;数学 思路…

一图总结:华为销售体系(铁三角组织LTC流程)

《华为铁三角工作法》阅读了多遍&#xff0c;花了些时间整理了一张图对本书的框架性总结&#xff0c;从流程&#xff08;LTC&#xff09;、组织&#xff08;铁三角&#xff09;、激励和管理三个大方面概览华为销售体系。 核心是一靠流程&#xff0c;二靠团队&#xff0c;而前提…

云尚办公-0.1.0

二、用户管理接口 1. 建表 角色与用户是多对多的关系&#xff0c;所以除了角色表和用户表外&#xff0c;还需要第三张表表示这两者间的对应关系。关系表中的用户id和角色id分别以对应表中的id作为外键。 CREATE TABLE sys_user (id BIGINT(20) NOT NULL AUTO_INCREMENT COM…

Windows Docker 部署 SQL Server

部署 SQL Server 打开 Docker Desktop&#xff0c;切换到 Linux 内核。然后在 PowerShell 执行下面命令&#xff0c;即可启动一个 SQL Server 服务&#xff0c;这里安装的是 2022 年版本 docker run -e "ACCEPT_EULAY" -e "MSSQL_SA_PASSWORDSQL123abcABC!&qu…

linuxlsof详解

lsof 是 List Open File 的缩写, 它主要用来获取被进程打开文件的信息&#xff0c;我们都知道&#xff0c;在Linux中&#xff0c;一切皆文件&#xff0c;lsof命令可以查看所有已经打开了的文件&#xff0c;比如: 普通文件&#xff0c;目录&#xff0c;特殊的块文件&#xff0c;…

【摸鱼日常】使用Docker部署2048小游戏

一、本次实践介绍 ​1. 本次实践简介 本次实践部署环境为个人测试环境&#xff0c;快速使用docker部署2048小游戏。 rootWellDone:/home/goodjob# uname -aLinux WellDone 6.5.0-14-generic #14~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Mon Nov 20 18:15:30 UTC 2 x86_64 x86_64…

NFS服务器挂载失败问题

问题 mount.nfs: requested NFS version or transport protocol is not supported背景&#xff1a;现在做嵌入式开发&#xff0c;需要在板端挂载服务器&#xff0c;读取服务器文件。挂载中遇到该问题。 挂载命令长这样 mount -t nfs -o nolock (XXX.IP):/mnt/disk1/zixi01.ch…

Python GUI开发库之nicegui使用详解

概要 在 Python 中,创建图形用户界面(GUI)应用程序通常需要大量的代码和时间。然而,随着 Python 生态系统的不断发展,出现了一些简化 GUI 开发过程的工具和库。其中之一就是 NiceGUI 库。本文将深入探讨 NiceGUI 库的功能、用法以及如何利用它来创建漂亮而功能丰富的 GUI…

C语言基础18 循环

们可能需要多次执行同一块代码。一般情况下&#xff0c;语句是按顺序执行的&#xff1a;函数中的第一个语句先执行&#xff0c;接着是第二个语句&#xff0c;依此类推。 编程语言提供了更为复杂执行路径的多种控制结构。 循环语句允许我们多次执行一个语句或语句组&#xff0…

LeetCode_Java_动态规划系列(3)(题目+思路+代码)

338.比特位计数 给你一个整数 n &#xff0c;对于 0 < i < n 中的每个 i &#xff0c;计算其二进制表示中 1 的个数 &#xff0c;返回一个长度为 n 1 的数组 ans 作为答案。 class Solution {public int[] countBits(int n) {/** 思路&#xff1a;* 1.创建一个长度为 n…

【Java程序设计】【C00329】基于Springboot的高校实习管理系统(有论文)

基于Springboot的高校实习管理系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的高校实习管理系统&#xff0c;本系统有管理员、公司、老师和学生四种角色&#xff1b; 管理员&#xff1a;个人中心、公司管理、…

Leetcode—64. 最小路径和【中等】

2024每日刷题&#xff08;116&#xff09; Leetcode—64. 最小路径和 实现代码 class Solution { public:int minPathSum(vector<vector<int>>& grid) {int m grid.size();int n grid[0].size();vector<vector<int>> dp(m 1, vector<int&g…

为什么会对猫毛过敏?如何缓解?浮毛克星—宠物空气净化器推荐

猫咪过敏通常是因为它们身上的Fel d1蛋白质导致的&#xff0c;这些蛋白质附着在猫咪的皮屑上。猫咪舔毛的过程会带出这些蛋白质&#xff0c;一旦接触就可能引发过敏症状&#xff0c;比如打喷嚏等。因此&#xff0c;减少空气中的浮毛数量有助于减轻过敏现象。猫用空气净化器可以…