如何在Spring Batch中高效处理S3存储的2GB级大CSV文件
解决方案
问题根因
你当前的实现直接用InputStreamResource包装S3流,该Resource没有实现可重读接口,且无法返回准确的内容长度,Spring Batch的FlatFileItemReader为了支持任务重启回溯,默认会缓存流内容到内存,所以会把2GB全量加载,导致内存占用过高。
方案1:使用Spring Cloud AWS S3Resource(推荐)
Spring Cloud AWS提供的S3Resource原生支持S3文件分块拉取,无需全量加载到内存,也不需要手动管理流生命周期,是最优方案。
首先引入对应依赖(Maven示例):
<dependency> <groupId>io.awspring.cloud</groupId> <artifactId>spring-cloud-aws-starter-s3</artifactId> <version>3.1.0</version> </dependency>
改造后的Reader代码:
@Bean public FlatFileItemReader<Employee> reader() { // 直接构造S3Resource,无需手动获取流,底层自动分块拉取 S3Resource s3Resource = new S3Resource("s3://testBucket/employee.csv"); FlatFileItemReader<Employee> reader = new FlatFileItemReader<>(); reader.setResource(s3Resource); reader.setLinesToSkip(1); // 增大缓冲区到128KB,减少S3请求次数 reader.setBufferSize(1024 * 128); // 不需要任务重启能力可关闭saveState,减少额外开销 // reader.setSaveState(false); reader.setLineMapper(new DefaultLineMapper() { { setLineTokenizer(new DelimitedLineTokenizer() { { setNames(Employee.fields()); } }); setFieldSetMapper(new BeanWrapperFieldSetMapper<Employee>() { { setTargetType(Employee.class); } }); } }); return reader; }
方案2:临时文件中转(无额外依赖)
如果不想引入Spring Cloud AWS依赖,可以先把S3文件分块写入本地临时文件,再用FileSystemResource传给FlatFileItemReader,同样不会全量占内存:
@Bean public FlatFileItemReader<Employee> reader() throws IOException { S3ObjectInputStream inputStream = getFileFromS3.dowloadFile("employee.csv", "testBucket", "us-east-1"); // 创建临时文件,JVM退出时自动删除,也可以处理完手动删除 File tempFile = File.createTempFile("s3-csv-", ".tmp"); tempFile.deleteOnExit(); // 分块写入临时文件,单次只占固定缓冲区内存 try (FileOutputStream out = new FileOutputStream(tempFile)) { byte[] buffer = new byte[1024 * 64]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { out.write(buffer, 0, bytesRead); } } finally { inputStream.close(); } FlatFileItemReader<Employee> reader = new FlatFileItemReader<>(); reader.setResource(new FileSystemResource(tempFile)); reader.setBufferSize(1024 * 128); reader.setLinesToSkip(1); // 其余LineMapper配置和原有逻辑一致 reader.setLineMapper(new DefaultLineMapper() { { setLineTokenizer(new DelimitedLineTokenizer() { { setNames(Employee.fields()); } }); setFieldSetMapper(new BeanWrapperFieldSetMapper<Employee>() { { setTargetType(Employee.class); } }); } }); return reader; }
额外优化建议
- 调整chunk大小:当前配置chunk=10过小,会导致频繁的事务提交和IO交互,建议调整到100~1000,可根据单条数据的大小灵活调整,内存充足的情况下可以更大。
- 按需使用S3 Select:如果你的业务只需要处理CSV中部分字段或符合筛选条件的行,可以直接调用S3 Select接口在S3服务端过滤数据,大幅减少需要传输的数据量,性能提升最明显。
- 多线程处理:如果处理逻辑是CPU密集型,可以开启多线程异步处理Chunk,注意
FlatFileItemReader不是线程安全的,多线程场景需要用SynchronizedItemStreamReader包装。
内容的提问来源于stack exchange,提问作者Mike Marsh
相关产品推荐
相关产品推荐

