我能否在没有Content-Length头的情况下将文件上传流式传输到S3?

95
我正在使用内存有限的机器,希望以流式方式上传动态生成(非磁盘文件)的文件到S3。换句话说,当我开始上传时,我不知道文件大小,但最终会知道。通常PUT请求有Content-Length头,但也许有一种方法可以绕过此限制,例如使用multipart或chunked content-type。
S3可以支持流式上传。例如,请参见此处:

http://blog.odonnell.nu/posts/streaming-uploads-s3-python-and-poster/

我的问题是,我是否可以在上传开始时不必指定文件长度来完成相同的事情?


smart_open Python库可以为您完成这项任务(流式读写)。 - Radim
3
十年过去了,AWS S3 SDKs仍然没有一种受控的方式来完成这个功能 - 作为一个在AWS生态系统中投入巨大的人,看到这一点与其他云服务提供商提供的对象管理SDK相比非常令人失望。这是一个核心功能缺失。 - Ermiya Eskandary
@ErmiyaEskandary 实际上,Go SDK 已经有了这个功能,但是 v1 和 v2 都存在多部分上传方法(uploader.Upload)的内存泄漏问题。 - Nikolay Dimitrov
6个回答

92
你需要通过S3的多部分API以5MiB+的块上传文件。每个块都需要Content-Length,但您可以避免将大量数据(100MiB +)加载到内存中。
  • 启动S3 Multipart Upload。
  • 将数据收集到缓冲区中,直到该缓冲区达到S3的较小的块大小限制(5MiB)。在构建缓冲区时生成MD5校验和。
  • 将该缓冲区作为Part上传,并存储ETag(请阅读相关文档)。
  • 一旦到达数据的EOF,上传最后一个块(可以小于5MiB)。
  • 完成Multipart Upload。

S3允许最多10,000个部分,因此选择5MiB的部分大小可以上传高达50GiB的动态文件。对于大多数用例来说应该足够了。

但是:如果您需要更多,则必须增加部分大小。可以通过使用更高的部分大小(例如10MiB)或在上传过程中增加部分大小来实现。

First 25 parts:   5MiB (total:  125MiB)
Next 25 parts:   10MiB (total:  375MiB)
Next 25 parts:   25MiB (total:    1GiB)
Next 25 parts:   50MiB (total: 2.25GiB)
After that:     100MiB

这将使您能够上传高达1TB的文件(目前S3单个文件的限制为5TB),而不会浪费不必要的内存。


关于 link to Sean O'Donnell's 博客的说明:

他的问题与你的不同 - 他在上传之前知道并使用Content-Length。他希望在这种情况下进行改进:许多库通过将文件中的所有数据加载到内存中来处理上传。伪代码如下:

data = File.read(file_name)
request = new S3::PutFileRequest()
request.setHeader('Content-Length', data.size)
request.setBody(data)
request.send()

他的解决方案是通过文件系统API获取Content-Length,然后将数据从磁盘流式传输到请求流中。伪代码如下:
upload = new S3::PutFileRequestStream()
upload.writeHeader('Content-Length', File.getSize(file_name))
upload.flushHeader()

input = File.open(file_name, File::READONLY_FLAG)

while (data = input.read())
  input.write(data)
end

upload.flush()
upload.close()

1
s3distcp中存在一个Java实现,以OutputStream的形式呈现。请参见https://github.com/libin/s3distcp/blob/master/src/main/java/com/amazon/external/elasticmapreduce/s3distcp/MultipartUploadOutputStream.java - sigget
2
我已经在https://github.com/alexmojaki/s3-stream-upload创建了一个专门用于此目的的开源库。 - Alex Hall
1
你在哪里找到了5MiB的限制? - Landon Kuhn
1
看起来现在你也可以使用管道命令行了 - https://github.com/aws/aws-cli/pull/903 - chrismarx
@AlexHall 谢谢,我找到了解决方法。这是我正在尝试解决的实际问题 https://stackoverflow.com/questions/61696155/python-boto3-multipart-upload-video-to-aws-s3。 如果文件已经在磁盘上,我可以做到这一点...但我想上传流式帧。 - Tushar Kolhe
显示剩余2条评论

9

为了帮助他人,将这个答案放在这里:

如果您不知道要流式上传到S3的数据长度,可以使用 S3FileInfo 及其 OpenWrite() 方法将任意数据写入S3。

var fileInfo = new S3FileInfo(amazonS3Client, "MyBucket", "streamed-file.txt");

using (var outputStream = fileInfo.OpenWrite())
{
    using (var streamWriter = new StreamWriter(outputStream))
    {
        streamWriter.WriteLine("Hello world");
        // You can do as many writes as you want here
    }
}

2
这些类有没有 Java 的等价物? - Steve K
“Hello world”的长度不是已知的吗?如果输入是流,它能工作吗? - at0mzk
根据微软的说法,由于Amazon.S3.IO API的同步性质,在dotnet core中不受支持。 - xiaochuanQ

7
您可以使用gof3r命令行工具来直接流式传输Linux管道内容:
$ tar -czf - <my_dir/> | gof3r put --bucket <s3_bucket> --key <s3_object>

有没有一种方法只需执行 tar -czf - <my_dir/> | aws s3 --something-or-other - user11810894

2

如果您正在使用Node.js,可以使用像s3-streaming-upload这样的插件来轻松实现此操作。


1

参考链接:https://github.com/aws/aws-cli/pull/903

以下是简介: 要将stdin流上传到s3,请使用: aws s3 cp - s3://my-bucket/stream

要将s3对象下载为stdout流,请使用: aws s3 cp s3://my-bucket/stream -

例如,如果我有对象s3://my-bucket/stream,我可以运行此命令: aws s3 cp s3://my-bucket/stream - | aws s3 cp - s3://my-bucket/new-stream

我的命令:

echo "ccc" | aws --endpoint-url=http://172.22.222.245:80 --no-verify-ssl s3 cp - s3://test-bucket/ccc


是的,这些天这个有效。 - Nikolay Dimitrov

1

更多关于HTTP多部分实体请求的信息,请参考。您可以将文件作为数据块发送到目标。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接