使用AWS S3 Java将ZipOutputStream上载到S3而不将zip文件(大)临时保存到磁盘

pan*_*kaj 8 java amazon-s3 zipoutputstream aws-lambda aws-java-sdk

我需要从S3下载照片(不在同一目录中),将其压缩,然后再次使用AWS S3 Java SDK上传到S3。该zip文件大小可以以GB为单位。目前,我正在使用AWS Lambda,它的临时存储限制为最大500 MB。因此,我不想将ZIP文件保存在磁盘上,而是想将ZIP文件(使用从S3下载的照片动态创建的ZIP文件)直接传输到S3。我需要使用AWS S3 Java SDK。

mad*_*ead 4

基本思想是使用流操作。这样,您就不必等到文件系统上生成 ZIP,而是在 ZIP 算法生成任何数据时立即开始上传。显然,一些数据会缓冲在内存中,仍然不需要等待整个 ZIP 在磁盘上生成。我们还将使用流组合和PipedInputStream/PipedOutputStream:一个用于读取数据,另一个用于压缩内容。

的版本:

final AmazonS3 client = AmazonS3ClientBuilder.defaultClient();

final PipedOutputStream pipedOutputStream = new PipedOutputStream();
final PipedInputStream pipedInputStream = new PipedInputStream(pipedOutputStream);

final Thread s3In = new Thread(() -> {
    try (final ZipOutputStream zipOutputStream = new ZipOutputStream(pipedOutputStream)) {
        S3Objects
                // It's just a convenient way to list all the objects. Replace with you own logic.
                .inBucket(client, "bucket")
                .forEach((S3ObjectSummary objectSummary) -> {
                    try {
                        if (objectSummary.getKey().endsWith(".png")) {
                            System.out.println("Processing " + objectSummary.getKey());

                            final ZipEntry entry = new ZipEntry(
                                    UUID.randomUUID().toString() + ".png" // I'm too lazy to extract file name from the
                                    // objectSummary
                            );

                            zipOutputStream.putNextEntry(entry);

                            IOUtils.copy(
                                    client.getObject(
                                            objectSummary.getBucketName(),
                                            objectSummary.getKey()
                                    ).getObjectContent(),
                                    zipOutputStream
                            );

                            zipOutputStream.closeEntry();
                        }
                    } catch (final Exception all) {
                        all.printStackTrace();
                    }
                });
    } catch (final Exception all) {
        all.printStackTrace();
    }
});
final Thread s3Out = new Thread(() -> {
    try {
        client.putObject(
                "another-bucket",
                "previews.zip",
                pipedInputStream,
                new ObjectMetadata()
        );

        pipedInputStream.close();
    } catch (final Exception all) {
        all.printStackTrace();
    }
});

s3In.start();
s3Out.start();

s3In.join();
s3Out.join();
Run Code Online (Sandbox Code Playgroud)

但请注意,它会打印一条警告:

WARNING: No content length specified for stream data.  Stream contents will be buffered in memory and could result in out of memory errors.
Run Code Online (Sandbox Code Playgroud)

这是因为 S3 需要在上传之前提前知道数据的大小。提前知道生成的 ZIP 的大小是不可能的。您可以尝试使用分段上传,但代码会更加棘手。不过,想法是相似的:一个线程应该读取数据并以 ZIP 流的形式发送内容,另一个线程应该读取 ZIPped 条目并将它们作为分段上传。上传所有条目(部分)后,应完成多部分。

的示例:

WARNING: No content length specified for stream data.  Stream contents will be buffered in memory and could result in out of memory errors.
Run Code Online (Sandbox Code Playgroud)

它也遭受着同样的困扰:在上传之前需要在内存中准备 ZIP。

如果您有兴趣,我准备了一个演示项目,您可以尝试一下代码。