一次性将大量文件上传到AWS S3

4
我有一个app需要上传超过100,000个文件(每个文件大小为1MB)到S3 Bucket。我知道S3有API用于上传大文件,但不确定是否有API可用于上传大量文件。
我目前正在使用putObject并尝试upload API将我的文件上传到S3。问题是上传速度太慢(在浏览器超时之后才完成),且使用了大量内存(超过允许的512MB限制)。
保存文件的代码如下:
saveFile: async (fileUrl, data) => {
    await s3.putObject({
        Bucket: bucket,
        Key: fileUrl,
        Body: JSON.stringify(data)
    }).promise();
},

在另一个地方,我把saveFile放在了一个循环中,像这样:
for (let file of files) {
    await saveFile(file.url, file.data);
}

我搜索了一下解决方案,发现stream可以通过减少内存需求来帮助解决问题,但我想知道在时间方面是否有任何差异?如果有的话,该如何实现?谢谢。
2个回答

6

通常我更喜欢使用托管上传API,而不是putObject方法。它可以处理大文件的多部分上传,同时支持流(因为putObject需要总文件大小,所以不能使用流)。

例如,在Node中:

const fs = require('fs');
const AWS = require('aws-sdk');
const s3 = new AWS.S3({});

s3.upload({
  Bucket: 'xxx',
  Key: 'fileName.png',
  Body: fs.createReadStream('/home/bar/Desktop/fileName.png')
}).promise(); // or callback

这可能会解决您的内存问题,但可能不会加快上传速度。 使用for循环的问题在于它将一个接一个地串行上传对象。相反,您可以使用await Promise.all([/* your list*/].map(/* ... */)),这将并行执行所有上传操作,但是,100,000远远太大了。
我建议使用像async这样的库,它具有处理组异步操作的许多有用方法。 例如,您可以使用cargoqueue方法,您的代码将如下所示:
const PARALLEL_UPLOADS = 10;
const q = async.queue((task, callback) => {
  s3.upload({
    Bucket: 'xxx',
    Key: task.dest,
    Body: fs.createReadStream(task.src)
  }, callback)
}, PARALLEL_UPLOADS);

q.drain = function() {
    console.log('all items have been processed');
};

q.push([
    { src: 'image1.png', dest: 'images/image1.png' },
    { src: 'image2.png', dest: 'images/image2.png' },
]);

这将同时上传您的所有文件,最多可同时上传10个项目。
希望这可以帮到您。

谢谢你,Simone。最近我使用了更多的js,所以了解async非常好。顺便说一下,你的解释很好,我非常感激。你提到的forEach应该是按顺序运行,对吗?或者你提到的是来自任何外部库/模块的forEach? - James H.
PS:现在它运行得非常快,有时会达到内存限制(1或2次),但不像在循环中使用嵌套putObject那样频繁,同时充分利用其最佳运行状态。非常好的Simone! - James H.
很高兴我可以帮到你。 当我写 forEach 时,实际上是指普通的 [ ].map,包裹在一个 await Promise.all() 中。这将导致大量请求 "并行" 进行,它不会等待一个请求完成才处理下一个请求(因为 await 关键字在其中没有意义)。 上面的回答已更新以引用 Promise.all 而不是 forEach - Simone Lusenti

0
const AWS = require('aws-sdk');
const fs = require('graceful-fs'); // from node.js
const path = require('path'); // from node.js
const queue = require('async-promise-queue');
const s3 = new AWS.S3();

const pushS3 = (srcFolderPath, destFolderPath) => {
  const uploadPromise = [];
  console.log(`Pushing ${srcFolderPath} to S3`);

  const files = fs.readdirSync(srcFolderPath);
    if (!files || files.length === 0) throw new Error(`provided folder '${srcFolderPath}' is empty or does not exist.`);

  // for each file in the directory
  for (const fileName of files) {
    // get the full path of the file
    const filePath = path.join(srcFolderPath, fileName);

   // ignore if directory
   if (fs.lstatSync(filePath).isDirectory()) {
     continue;
   }

   uploadPromise.push({
     src: filePath,
     dest: `${destFolderPath}${fileName}`,
   });
  }
  const worker = queue.async.asyncify(task => s3.upload({
    Bucket: AWS_BUCKET,
    Key: task.dest,
    Body: fs.createReadStream(task.src),
  }).promise());

  return queue(worker, uploadPromise, 10000);
};

pushS3('sourcePath', 'destinationS3Path')
.then(()=>{console.log('Sucessfully Transferred to S3');})
.catch((err)=>{console.error(err);})

上面Simone Lusenti's的解决方案的Promise实现。在我的情况下,我有50000多个文件。我试图从AWS ECS将这些文件放入S3中。我之前遇到了2个错误。EMFILE too many files open错误通过使用graceful-fs模块得到解决,AWS ECS中的Missing Credentials Error则通过使用async-promise-queue模块得到解决。

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