Java文件上传到S3-多部分应该加快速度吗



我们使用Java 8和AWS SDK以编程方式将文件上传到AWS S3。对于上传大文件(>100MB(,我们了解到首选的上传方法是Multipart Upload。我们尝试过,但似乎并没有加快速度,上传时间几乎与不使用多部分上传相同。更糟糕的是,我们甚至遇到内存不足的错误,说堆空间不够。

问题:

  1. 使用多部分上传真的应该加快上传速度吗?如果没有,为什么要使用它
  2. 为什么使用多部分上传比不使用更快地消耗内存?它是否同时上传所有部分

我们使用的代码如下:

private static void uploadFileToS3UsingBase64(String bucketName, String region, String accessKey, String secretKey,
String fileBase64String, String s3ObjectKeyName) {

byte[] bI = org.apache.commons.codec.binary.Base64.decodeBase64((fileBase64String.substring(fileBase64String.indexOf(",")+1)).getBytes());
InputStream fis = new ByteArrayInputStream(bI);

long start = System.currentTimeMillis();
AmazonS3 s3Client = null;
TransferManager tm = null;
try {
s3Client = AmazonS3ClientBuilder.standard().withRegion(region)
.withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKey, secretKey)))
.build();

tm = TransferManagerBuilder.standard()
.withS3Client(s3Client)
.withMultipartUploadThreshold((long) (50* 1024 * 1025))
.build();
ObjectMetadata metadata = new ObjectMetadata();
metadata.setHeader(Headers.STORAGE_CLASS, StorageClass.Standard);
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, s3ObjectKeyName,
fis, metadata).withSSEAwsKeyManagementParams(new SSEAwsKeyManagementParams());

Upload upload = tm.upload(putObjectRequest);
// Optionally, wait for the upload to finish before continuing.
upload.waitForCompletion();
long end = System.currentTimeMillis();
long duration = (end - start)/1000;

// Log status
System.out.println("Successul upload in S3 multipart. Duration = " + duration);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (s3Client != null)
s3Client.shutdown();
if (tm != null)
tm.shutdownNow();
}
}

如果同时上传多个部分,使用multipart只会加快上传速度。

在您的代码中,您正在设置withMultipartUploadThreshold。如果您的上传大小大于该阈值,那么您应该观察单独部分的并发上传。如果不是,则只应使用一个上传连接。你是说你有>100MB文件,在您的代码中,您有50*1024*1025=52480000字节作为多部分上传阈值,因此应该同时上传该文件的部分。

但是,如果您的上传吞吐量受到网络速度的限制,则吞吐量不会增加。这可能是你没有观察到任何速度增加的原因。

使用multipart还有其他原因,因为出于容错原因,建议也使用multipart。此外,它的最大大小比单次上传更大。

有关更多详细信息,请参阅文档:

多部分上传允许您将单个对象作为零件。每个部分都是对象数据的连续部分。你可以以任何顺序独立上传这些对象部分。如果任何部件的传输失败,您可以在没有影响其他部分。上传完对象的所有部分后,AmazonS3组装这些部件并创建对象。一般来说当对象大小达到100MB时,应该考虑使用多部分上传,而不是在单个文件中上传对象活动

使用多部分上传提供以下优点:

  • 提高吞吐量-您可以并行上传部件以提高吞吐量。

  • 从任何网络问题中快速恢复-较小的部件尺寸最大限度地减少了由于网络原因重新启动失败上传的影响错误

  • 暂停并恢复对象上传-您可以随时间上传对象部分。启动多部分上传后,不会过期;你必须显式完成或停止多部分上载。

  • 在你知道最终对象大小之前开始上传-你可以在创建对象时上传。

我们建议您以以下方式使用多部分上传:

  • 如果您正在通过稳定的高带宽网络上传大型对象,请使用多部分上传来最大限度地利用可用的多线程并行上传对象部分的带宽表演

  • 如果您通过不稳定的网络进行上传,请使用多部分上传,以避免重新启动上传,从而提高对网络错误的恢复能力。当使用多部分上传时,您需要重试只上传部分在上传过程中被中断。您不需要重新启动从一开始就上传您的对象。

eis的答案非常好。尽管你仍然应该采取一些行动:

  • String.getBytes(StandardCharsets.US_ASCII)ISO_8859_1防止使用更昂贵的编码,如UTF-8。如果平台编码将是UTF-16LE,则数据甚至将被破坏(0x00字节(
  • 标准的javaBase64有一些可能工作的de-encoder。它可以在字符串上工作。但是,请检查正确的处理方式(行尾(
  • 在出现异常/内部返回的情况下,try with resources也会关闭
  • ByteArrayInputStream没有关闭,这会是更好的风格(更容易垃圾收集?(
  • 您可以将ExecutorFactory设置为线程池工厂,以限制全局线程数

所以

byte[] bI = Base64.getDecoder().decode(
fileBase64String.substring(fileBase64String.indexOf(',') + 1));
try (InputStream fis = new ByteArrayInputStream(bI)) {
...
}