如何用Java从S3同目录分批按数量下载多文件
针对S3批量按数量限制下载小文件的优化方案
核心思路:分批次列举+批量下载控制
1. 改进listObjectsV2的分页与批量处理
listObjectsV2本身支持分页(通过MaxKeys参数控制单次列举数量),S3允许单次最大列举1000个对象,所以可以循环分页获取,每攒够20000个就启动一批下载任务,同时通过线程池控制并发数,避免内存过载。
关键优化点:
- 设置
MaxKeys=1000,循环调用直到收集到足够数量的文件或列举完成 - 每收集满20000个对象键后,提交独立的下载批次任务,同时清空当前列表,避免内存堆积
- 根据机器CPU、网络资源调整线程池大小(建议50-100),避免无限制开线程
- 设置
Java示例代码片段:
AmazonS3 s3Client = AmazonS3ClientBuilder.defaultClient(); ListObjectsV2Request listReq = new ListObjectsV2Request() .withBucketName("your-bucket") .withPrefix("target-directory/") .withMaxKeys(1000); List<String> objectKeys = new ArrayList<>(); final int BATCH_SIZE = 20000; ExecutorService executor = Executors.newFixedThreadPool(80); // 按需调整 do { ListObjectsV2Result listRes = s3Client.listObjectsV2(listReq); for (S3ObjectSummary obj : listRes.getObjectSummaries()) { objectKeys.add(obj.getKey()); // 达到批量阈值则启动下载 if (objectKeys.size() == BATCH_SIZE) { executor.submit(() -> downloadBatch(s3Client, objectKeys, "local-save-path")); objectKeys = new ArrayList<>(); // 重置列表 } } listReq.setContinuationToken(listRes.getNextContinuationToken()); } while (listRes.isTruncated()); // 处理剩余不足一批的文件 if (!objectKeys.isEmpty()) { executor.submit(() -> downloadBatch(s3Client, objectKeys, "local-save-path")); } executor.shutdown(); executor.awaitTermination(1, TimeUnit.HOURS);配套的批量下载方法(可在批次内再用小线程池优化):
private static void downloadBatch(AmazonS3 s3Client, List<String> keys, String localDir) { File dir = new File(localDir); if (!dir.exists()) dir.mkdirs(); ExecutorService batchExecutor = Executors.newFixedThreadPool(20); for (String key : keys) { batchExecutor.submit(() -> { String fileName = key.substring(key.lastIndexOf("/") + 1); File localFile = new File(dir, fileName); s3Client.getObject(new GetObjectRequest("your-bucket", key), localFile); }); } batchExecutor.shutdown(); try { batchExecutor.awaitTermination(30, TimeUnit.MINUTES); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }
2. 用自定义过滤器限制TransferManager下载数量
transferManager.downloadDirectory默认会下载整个目录,但可以通过自定义S3ObjectFilter来控制下载数量,避免OutOfMemoryError:
- Java示例代码片段:
注意:如果需要多批次执行,要重置计数器,并通过调整前缀或延续标记跳过已下载的文件,保证下载顺序稳定。TransferManager tm = TransferManagerBuilder.standard().withS3Client(s3Client).build(); AtomicInteger downloadCounter = new AtomicInteger(0); final int MAX_DOWNLOADS = 20000; MultipleFileDownload downloadTask = tm.downloadDirectory( "your-bucket", "target-directory/", new File("local-save-path"), new S3ObjectFilter() { @Override public boolean accept(S3ObjectSummary objectSummary) { return downloadCounter.incrementAndGet() <= MAX_DOWNLOADS; } } ); downloadTask.waitForCompletion(); tm.shutdownNow();
3. 进一步性能优化建议
- 启用S3 Transfer Acceleration:跨区域下载时,开启后可大幅提升小文件传输速度
- 优化线程池参数:根据机器核心数、带宽调整线程数,避免过多线程导致上下文切换开销
- 本地存储优化:使用SSD存储目标文件,减少磁盘写入延迟;机械硬盘可调整文件系统参数提升批量写入效率
- 复用S3客户端:确保
AmazonS3客户端单例复用,避免频繁创建连接的开销 - 启用HTTP/2:新版AWS SDK默认支持HTTP/2,能提升并发小文件的传输效率
内容的提问来源于stack exchange,提问作者Hemangi Patil
相关产品推荐
相关产品推荐

