AWS Java SDK S3TransferManager上传大文件如何避免全量读入内存?
我正在开发一个应用,需从无法控制的外部网站下载大文件,再使用AWS Java SDK v2将其副本上传至S3存储桶(还有其他操作),S3TransferManager由Spring Batch注入。
下载及上传代码如下:
URL website = new URL("https://big_website_url"); HttpURLConnection huc = (HttpURLConnection)website.openConnection(); try(ReadableByteInputChannel rbc = Channels.newChannel(huc.getInputStream()); FileOutputStream fos = new FileOutputStream("filename.zip"))) { fos.getChannel().transferFrom(rbc, 0, huc.getContentLengthLong()); } huc.disconnect(); String uploadFileKey = "my/download/path/filename.zip"; UploadFileRequest uploadFileRequest = UploadFileRequest.builder() .putObjectRequest(b -> b.bucket("mybucket").key(uploadFileKey)) .addTransferListener(LoggingTransferListener.create()) .source(Paths.get("filename.zip")) .build(); FileUpload fileUpload = s3TransferManager.uploadFile(uploadFileRequest);
文件大小可能超过900MB,我使用Spring Boot的Docker镜像插件生成在Kubernetes中运行的应用:
mvn spring-boot:boot-image
Spring Boot生成的镜像最大直接内存缓冲区大小为10MB,我虽可修改该值,但希望尽可能降低容器内存占用。
问题在于我看到SDK文档描述:
"File will be read entirely and may be read multiple times in the event of a retry."
担心S3TransferManager会将整个文件读入内存后再上传,在文件达900MB、容器内存限制为500MB的情况下,是否可让S3TransferManager分块读取文件上传,还是必须退回到低阶分片上传请求?
不需要退回到低阶分片上传API,S3TransferManager针对本地文件的上传场景,默认支持自动分块上传,不会将整个文件加载到内存中,具体说明和配置如下:
默认分块行为说明
当使用UploadFileRequest且源为本地文件时,SDK默认会在文件大小≥8MB时触发分块上传逻辑,每个分块会被单独读取并上传,内存中仅保留当前处理的分块数据,内存占用仅为单分块大小加上少量SDK开销,完全符合你500MB内存限制的要求。你看到的文档描述是针对从InputStream上传的场景,而非本地文件上传。显式配置分块大小
如果需要更精准控制内存占用,可以显式设置最小分块大小(S3要求分块大小最小为5MB,最后一块除外),示例代码如下:
UploadFileRequest uploadFileRequest = UploadFileRequest.builder() .putObjectRequest(b -> b.bucket("mybucket").key(uploadFileKey)) .addTransferListener(LoggingTransferListener.create()) .source(Paths.get("filename.zip")) .minimumPartSize(5 * 1024 * 1024) // 设置为5MB分块 .build();
- 可选优化:跳过本地文件直接上传
如果你的业务逻辑不需要保留本地文件副本,可以直接从外部网站的输入流分块上传到S3,节省磁盘空间和IO开销,示例代码如下:
URL website = new URL("https://big_website_url"); HttpURLConnection huc = (HttpURLConnection)website.openConnection(); try (InputStream inputStream = huc.getInputStream()) { UploadRequest uploadRequest = UploadRequest.builder() .putObjectRequest(b -> b.bucket("mybucket").key(uploadFileKey)) .addTransferListener(LoggingTransferListener.create()) .requestBody(RequestBody.fromInputStream(inputStream, huc.getContentLengthLong())) .minimumPartSize(5 * 1024 * 1024) .build(); s3TransferManager.upload(uploadRequest).waitForCompletion(); } finally { huc.disconnect(); }
- 直接内存配置说明
当前Spring Boot镜像的10MB直接内存设置完全足够处理5MB分块的上传,无需调整该值,可保持容器内存占用最低。
内容的提问来源于stack exchange,提问作者Brad

