发布于 2026-01-06 4 阅读
0

使用 AWS Lambda 和 Node 将 Zip 文件存储在 S3 上

使用 AWS Lambda 和 Node 将 Zip 文件存储在 S3 上

本文于 2022 年 9 月 20 日更新,以提高处理大量文件时的可靠性。

  • 更新流处理机制,使其仅在文件准备好由 Zip 压缩程序处理时才打开 S3 流。这修复了处理大量文件时可能出现的超时问题。
  • 使用 S3 保持连接功能并限制连接的套接字数量。

将 S3 上的文件打包成 Zip 文件,以便用户一次性下载多个文件,这种需求并不少见。或许 AWS 将来也会提供这项功能。在此之前,您可以编写一个简单的脚本来实现。

如果你想在 AWS Lambda 等无服务器环境中提供此服务,则有两个主要限制条件决定了你可以采取的方法。

1 - /tmp 目录只有 512MB。你可能首先想到的是从 S3 下载文件,压缩后再上传。这种方法一开始没问题,直到你用临时文件把 /tmp 目录填满为止!

2 - 内存限制为 3GB。您可以将临时文件存储在堆内存中,但同样,内存也限制在 3GB。即使在普通的服务器环境中,您也不会希望一个简单的 zip 函数占用 3GB 的 RAM!

那么该怎么办呢?答案是将数据从 S3 流式传输,经过归档服务器后再传输回 S3。

幸运的是,这篇 Stack Overflow 帖子及其评论指明了方向,而这篇文章基本上是对其内容的重新阐述!

以下代码是 TypeScript 代码,但 Javascript 代码与之相同,只是去掉了类型定义。

首先,你需要导入以下模块。

import * as Archiver from 'archiver';
import * as AWS from 'aws-sdk';
import { createReadStream } from 'fs';
import { Readable, Stream } from 'stream';
import * as lazystream from 'lazystream';
Enter fullscreen mode Exit fullscreen mode

首先配置 aws-sdk,使其在与 S3 通信时使用 keepalive 机制,并限制最大连接数。这可以提高效率,并有助于避免意外达到连接数上限。您也可以AWS_NODEJS_CONNECTION_REUSE_ENABLED在 Lambda 环境中进行设置,而不是在此处进行配置。

    // Set the S3 config to use keep-alives
    const agent = new https.Agent({ keepAlive: true, maxSockets: 16 });

    AWS.config.update({ httpOptions: { agent } });
Enter fullscreen mode Exit fullscreen mode

我们首先创建从 S3 获取数据的流。为了防止 S3 超时,这些流被包装在“lazystream”中,这会延迟流的实际打开,直到归档程序准备好读取数据。

假设您有一个键列表keys。对于每个键,我们需要创建一个 ReadStream。为了跟踪这些键和流,我们创建一个 S3DownloadStreamDetails 类型。“filename”最终将是 Zip 文件中的文件名,因此您可以在此阶段对其进行任何必要的转换。

    type S3DownloadStreamDetails = { stream: Readable; filename: string };
Enter fullscreen mode Exit fullscreen mode

现在,对于我们的键数组,我们可以对其进行迭代以创建 S3StreamDetails 对象。

    const s3DownloadStreams: S3DownloadStreamDetails[] = keys.map((key: string) => {
        return {
            stream: new lazystream.Readable(() => {
                console.log(`Creating read stream for ${fileToDownload.key}`);
                return s3.getObject({ Bucket: s3UGCBucket, Key: fileToDownload.key }).createReadStream();
            }),
            filename: key,
        };
    });
Enter fullscreen mode Exit fullscreen mode

现在准备上传端,创建一个Stream.PassThrough对象并将其指定为参数的主体S3.PutObjectRequest


    const streamPassThrough = new Stream.PassThrough();
    const params: AWS.S3.PutObjectRequest = {
        ACL: 'private',
        Body: streamPassThrough
        Bucket: 'Bucket Name',
        ContentType: 'application/zip',
        Key: 'The Key on S3',
        StorageClass: 'STANDARD_IA', // Or as appropriate
    };

Enter fullscreen mode Exit fullscreen mode

现在我们可以开始上传过程了。

    const s3Upload = s3.upload(params, (error: Error): void => {
        if (error) {
            console.error(`Got error creating stream to s3 ${error.name} ${error.message} ${error.stack}`);
            throw error;
        }
    });

Enter fullscreen mode Exit fullscreen mode

例如,如果您想监控上传过程,以便向用户提供反馈,那么您可以httpUploadProgress像这样附加一个处理程序。

    s3Upload.on('httpUploadProgress', (progress: { loaded: number; total: number; part: number; key: string }): void => {
        console.log(progress); // { loaded: 4915, total: 192915, part: 1, key: 'foo.jpg' }
    });
Enter fullscreen mode Exit fullscreen mode

现在创建归档程序

    const archive = Archiver('zip');
    archive.on('error', (error: Archiver.ArchiverError) => { throw new Error(`${error.name} ${error.code} ${error.message} ${error.path} ${error.stack}`); });
Enter fullscreen mode Exit fullscreen mode

现在我们可以将归档器连接到上传流,并将所有下载流附加到该上传流。

    await new Promise((resolve, reject) => {

        console.log('Starting upload');

        s3Upload.on('close', resolve);
        s3Upload.on('end', resolve);
        s3Upload.on('error', reject);

        archive.pipe(s3StreamUpload);
        s3DownloadStreams.forEach((streamDetails: S3DownloadStreamDetails) => archive.append(streamDetails.stream, { name: streamDetails.filename }));
        archive.finalize();
    }).catch((error: { code: string; message: string; data: string }) => { throw new Error(`${error.code} ${error.message} ${error.data}`); });
Enter fullscreen mode Exit fullscreen mode

最后等待上传程序完成

    await s3Upload.promise();
Enter fullscreen mode Exit fullscreen mode

好了,大功告成。

我用超过 10GB 的压缩包测试过,效果非常好。希望对您有所帮助。

文章来源:https://dev.to/lineup-ninja/zip-files-on-s3-with-aws-lambda-and-node-1nm1