异步导出让 HTTP 请求先返回任务编号,后台生成文件,用户再查询状态与下载。异步只改变执行位置,不会自动降低内存,也不会自动获得失败重试和断点续传。
先做一个文件实验
下面的 Java 21 程序在后台写 10 万行 CSV,内存中不保存所有行。它没有数据库、HTTP 接口或 Excel 图片,先用它观察线程、输出文件与完成确认。
保存 AsyncCsvDemo.java:
import java.nio.charset.StandardCharsets;import java.nio.file.Files;import java.nio.file.Path;import java.util.concurrent.ArrayBlockingQueue;import java.util.concurrent.ThreadPoolExecutor;import java.util.concurrent.TimeUnit;
public class AsyncCsvDemo { public static void main(String[] args) throws Exception { Path output = Path.of("export.csv").toAbsolutePath(); Path partial = Path.of("export.csv.part").toAbsolutePath(); if (Files.exists(output) || Files.exists(partial)) { throw new IllegalStateException("输出已存在,请换一个空实验目录"); } var executor = new ThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2), new ThreadPoolExecutor.AbortPolicy()); try { var task = executor.submit(() -> { try { try (var writer = Files.newBufferedWriter(partial, StandardCharsets.UTF_8)) { writer.write("id,name\r\n"); for (int id = 1; id <= 100000; id++) { if (Thread.currentThread().isInterrupted()) { throw new InterruptedException("任务被取消"); } writer.write(id + ",demo-" + id + "\r\n"); } } Files.move(partial, output); return Files.size(output); } catch (Exception failure) { try { Files.deleteIfExists(partial); } catch (Exception cleanup) { failure.addSuppressed(cleanup); } throw failure; } }); System.out.println("后台任务已提交"); try { System.out.println("完成,字节数=" + task.get(30, TimeUnit.SECONDS)); } finally { if (!task.isDone()) task.cancel(true); } } finally { executor.shutdownNow(); executor.awaitTermination(5, TimeUnit.SECONDS); } }}javac -encoding UTF-8 AsyncCsvDemo.javajava AsyncCsvDemo(Get-Content export.csv | Measure-Object -Line).Lines预期有 100001 行,含一行表头。输出先写 .part,关闭后才移动为完成文件。真实 CSV 字段需要转义引号、逗号和换行,并根据使用场景处理表格公式注入,不能直接拼任意用户文本。
接进 Spring Boot
提交接口校验用户与查询参数,创建带所有者的任务记录,将工作提交给有界线程池,返回任务 ID。状态至少区分等待、运行、成功、失败、取消与过期;队列拒绝时应立即报告,不能留下永远等待的任务。
@Async 通过 Spring 代理生效,同一个对象内部直接调用方法不会转为异步。后台异常要进入任务结果;日志中没有异常不代表任务完成。下载接口重新检查所有者与文件状态,文件路径不由客户端指定。
大数据与 Excel
查询按稳定主键游标或一致性快照分页,明确导出期间数据更新的处理;简单 offset 分页可能漏行或重复。每批读取和写入后释放资源,图片下载设置超时、大小与并发限制。
POI 的 SXSSFWorkbook 可以限制内存中的行窗口,但图片、样式、共享字符串和临时文件仍有成本;一张 Excel 工作表最多 1048576 行,包含表头。实现分表/分文件、关闭与清理临时文件后,再用真实规模验证。
进程重启后本例任务消失。生产重试需要确定是重新生成文件还是从一致性检查点恢复,不能把失败后重新提交称为断点续传。
评论