使用 Ruby 将大文件上传到 S3 失败并出现内存不足错误,如何分块读取和上传?

Uploading Large File to S3 with Ruby Fails with Out of Memory Error, How to Read and Upload in Chunks?

我们正在从 Windows 机器上通过 Ruby AWS SDK (v2) 将各种文件上传到 S3。我们已经使用 Ruby 1.9 进行了测试。我们的代码工作正常,除非遇到大文件,抛出内存不足错误。

起初我们用这段代码将整个文件读入内存:

:body => IO.binread(filepath),

然后在谷歌搜索后我们发现有一些方法可以使用 Ruby:

分块读取文件
:body =>  File.open(filepath, 'rb') { |io| io.read },

虽然这段代码没有解决问题,但我们找不到具体的 S3(或相关)示例来说明如何读取文件并将其分块传递给 S3。整个文件仍加载到内存中,并在大文件时抛出内存不足错误。

我们知道我们可以将文件分成块并使用 AWS 分段上传上传到 S3,但是如果可能的话最好避免这种情况(尽管如果这是唯一的方式也没关系)。

我们的代码示例如下。分块读取文件、避免内存不足错误并上传到 S3 的最佳方法是什么?

require 'aws-sdk'

filepath = 'c:\path\to\some\large\file.big'
bucket = 's3-bucket-name'
s3key = 'some/s3/key/file.big'
accesskeyid = 'ACCESSKEYID'
accesskey = 'ACCESSKEYHERE'
region = 'aws-region-here'

s3 = Aws::S3::Client.new(
  :access_key_id => accesskeyid,
  :secret_access_key => accesskey,
  :region => region
  )

resp = s3.put_object(
  :bucket => bucket,
  :key => s3key,
  :body =>  File.open(filepath, 'rb') { |io| io.read },
  )

请注意,我们没有达到 S3 5GB 的限制,例如 1.5GB 的文件会发生这种情况。

.put 中存储桶的大小限制为 5GB。

但是在 s3 中有 "multipart" 上传功能,您可以在其中上传大尺寸的文件。

这些链接可能对您有所帮助: http://docs.aws.amazon.com/AmazonS3/latest/dev/UploadingObjects.html http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/S3/MultipartUpload.html

Ruby、aws-sdk gem 的 v2 AWS SDK 支持直接通过网络流式传输对象,而无需将它们加载到内存中。你的例子只需要一个小的修正就可以做到这一点:

File.open(filepath, 'rb') do |file|
  resp = s3.put_object(
   :bucket => bucket,
   :key => s3key,
   :body => file
  )
end

这是可行的,因为它允许 SDK 调用 #read 每次传递少量字节的文件对象。在 Ruby IO 对象(例如文件)上调用 #read 时不带第一个参数会将整个对象读入内存,并将其作为字符串返回。这就是导致内存不足错误的原因。

也就是说,aws-sdk gem 提供了另一个更有用的界面,用于将文件上传到 Amazon S3。此替代界面自动:

  • 对大对象使用 multipart APIs
  • 可以使用多线程并行上传分片,提高上传速度
  • 计算客户端数据的 MD5 以用于服务端数据完整性检查。

一个简单的例子:

# notice this uses Resource, not Client
s3 = Aws::S3::Resource.new(
  :access_key_id => accesskeyid,
  :secret_access_key => accesskey,
  :region => region
)

s3.bucket(bucket).object(s3key).upload_file(filepath)

这是 aws-sdk 资源接口的一部分。这里有很多有用的实用程序。客户端 class 仅提供基本 API 功能。