使用 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';
首先配置 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 } });
我们首先创建从 S3 获取数据的流。为了防止 S3 超时,这些流被包装在“lazystream”中,这会延迟流的实际打开,直到归档程序准备好读取数据。
假设您有一个键列表keys。对于每个键,我们需要创建一个 ReadStream。为了跟踪这些键和流,我们创建一个 S3DownloadStreamDetails 类型。“filename”最终将是 Zip 文件中的文件名,因此您可以在此阶段对其进行任何必要的转换。
type S3DownloadStreamDetails = { stream: Readable; filename: string };
现在,对于我们的键数组,我们可以对其进行迭代以创建 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,
};
});
现在准备上传端,创建一个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
};
现在我们可以开始上传过程了。
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;
}
});
例如,如果您想监控上传过程,以便向用户提供反馈,那么您可以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' }
});
现在创建归档程序
const archive = Archiver('zip');
archive.on('error', (error: Archiver.ArchiverError) => { throw new Error(`${error.name} ${error.code} ${error.message} ${error.path} ${error.stack}`); });
现在我们可以将归档器连接到上传流,并将所有下载流附加到该上传流。
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}`); });
最后等待上传程序完成
await s3Upload.promise();
好了,大功告成。
我用超过 10GB 的压缩包测试过,效果非常好。希望对您有所帮助。
文章来源:https://dev.to/lineup-ninja/zip-files-on-s3-with-aws-lambda-and-node-1nm1