【问题标题】:How can I use the AvroParquetWriter and write to S3 via the AmazonS3 api?如何使用 AvroParquetWriter 并通过 AmazonS3 api 写入 S3?
【发布时间】:2020-06-09 11:42:39
【问题描述】:

我目前正在使用下面的代码通过 Avro 编写镶木地板。此代码将其写入文件系统,但我想写入 S3。

try {
    StopWatch sw = StopWatch.createStarted();
    Schema avroSchema = AvroSchemaBuilder.build("pojo", message.getTransformedMessage().get(0));
    final String parquetFile = "parquet/data.parquet";
    final Path path = new Path(parquetFile);

    ParquetWriter writer = AvroParquetWriter.<GenericData.Record>builder(path)
        .withSchema(avroSchema)
        .withConf(new org.apache.hadoop.conf.Configuration())
        .withCompressionCodec(CompressionCodecName.SNAPPY)
        .withWriteMode(Mode.OVERWRITE)//probably not good for prod. (overwrites files).
        .build();

    for (Map<String, Object> row : message.getTransformedMessage()) {
      StopWatch stopWatch = StopWatch.createStarted();
      final GenericRecord record = new GenericData.Record(avroSchema);
      row.forEach((k, v) -> {
        record.put(k, v);
      });
      writer.write(record);
    }
    //todo:  Write to S3.  We should probably write via the AWS objects.  This does not show that.
    //https://stackoverflow.com/questions/47355038/how-to-generate-parquet-file-using-pure-java-including-date-decimal-types-an
    writer.close();
    System.out.println("Total Time: " + sw);

  } catch (Exception e) {
    //do somethign here.  retryable?  non-retryable?  Wrap this excetion in one of these?
    transformedParquetMessage.getOriginalMessage().getMetaData().addException(e);
  }

这可以很好地写入文件,但是如何让它流式传输到 AmazonS3 api 中?我在网上找到了一些使用 Hadoop-aws jar 的代码,但这需要一些 Windows exe 文件才能工作,当然,我们希望避免这种情况。目前我只使用:

 <dependency>
  <groupId>org.apache.avro</groupId>
  <artifactId>avro</artifactId>
  <version>1.9.2</version>
</dependency>
<dependency>
  <groupId>org.apache.parquet</groupId>
  <artifactId>parquet-avro</artifactId>
  <version>1.8.1</version>
</dependency>
<dependency>
  <groupId>org.apache.hadoop</groupId>
  <artifactId>hadoop-core</artifactId>
  <version>1.2.1</version>
</dependency>

所以问题是,有没有办法拦截 AvroParquetWriter 上的输出流,以便我可以将其流式传输到 S3? 我想要这样做的主要原因是重试。 S3 最多自动重试 3 次。这对我们有很大帮助。

【问题讨论】:

  • 在this 方式可能吗?尽管它使用了您想要避免的库/exe :( :)
  • 是的,这很接近,但它依赖于服务器上运行的 Hadoop。
  • 这恐怕不太可行。如果您查看source 类,您可以看到超类是 org.apache.parquet.hadoop.ParquetWriter,所以我会说(无需深入挖掘)配置的 hadoop 的存在是对此的要求.看来 Spark 可以是 better candidate for this。
  • 感谢您的回复!我真的在努力克服两件事:对 hadoop 的外部依赖和 S3 依赖。如果 parquet 只是一种文件格式,为什么写出来这么难?!
  • 您可以找到类似问题的一些答案here - 查看链接的 Jira 问题。

标签: java hadoop amazon-s3 avro parquet


【解决方案1】:

这确实取决于 hadoop-aws jar,所以如果您不愿意使用它,我不确定是否可以帮助您。但是,我在 mac 上运行并且没有任何 windows exe 文件,所以我不确定你说这些来自哪里。 AvroParquetWriter 已经依赖于 Hadoop,因此即使您无法接受这种额外的依赖,对其他人来说也可能没什么大不了的:

您可以使用 AvroParquetWriter 通过向其传递使用 URI 参数创建的 Hadoop 路径并设置正确的配置来直接流式传输到 S3。

val uri = new URI("s3a://<bucket>/<key>")
val path = new Path(uri)

val config = new Configuration()
config.set("fs.s3a.access.key", key)
config.set("fs.s3a.secret.key", secret)
config.set("fs.s3a.session.token", sessionToken)
config.set("fs.s3a.aws.credentials.provider", credentialsProvider)

val writer = AvroParquetWriter.builder[GenericRecord](path).withConf(config).withSchema(schema).build()

我使用了以下依赖项(sbt 格式):

"org.apache.avro" % "avro" % "1.8.1"
"org.apache.hadoop" % "hadoop-common" % "2.9.0"
"org.apache.hadoop" % "hadoop-aws" % "2.9.0"
"org.apache.parquet" % "parquet-avro" % "1.8.1"

【讨论】:

  • 这是正确的答案,但需要充实。请包括您使用的罐子。我们这样做了,发现只有 Windows 需要额外的 .exe 文件等。 Linux(AWS 上的 fargate)没有。这些 jars 由 AWS 更新和支持,文档很棒。干得好,菜鸟!
  • 据我所知,您不需要明确包含 avro 依赖项。它带有parquet-avro。
  • 我最终也在 Windows 上运行,并且在我的 HADOOP_HOME 环境变量路径中需要一个 winutils.exe 文件。
  • 这对我有用,对其他人来说可能很明显,但我必须致电 writer.close() 才能真正完成上传。
【解决方案2】:

希望我没有误解这个问题,但看来您正在做的是将 avro 转换为 parquet 并且您想将 parquet 上传到 s3

关闭 ParquetWriter 后,您应该调用如下所示的方法(假定这不会拦截从 avro 写入 parquet 的流,它只会流式传输不再写入的 parquet 文件):

        AmazonS3 s3Client = AmazonS3ClientBuilder.standard().withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials("ACCESS_KEY", "SECRET_KEY"))).build();
        S3Path outputPath = new S3Path();
        outputPath.setBucket("YOUR_BUCKET");
        outputPath.setKey("YOUR_FOLDER_PATH");
        try {
            InputStream parquetStream = new FileInputStream(new File(parquetFile));
            s3Client.putObject(outputPath.getBucket(), outputPath.getKey(), parquetStream, null);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

使用 AWS 开发工具包

<dependency>
    <groupId>com.amazonaws</groupId>
    <artifactId>aws-java-sdk</artifactId>
    <version>1.11.749</version>
</dependency>

当然,该方法将驻留在不同的 utils 类中,并且该方法的构造函数应使用凭证初始化 AmazonS3 s3Client,因此您需要做的就是调用并访问它的 s3Client 成员以放置对象

希望对你有帮助

【讨论】:

  • 很好的尝试,但这只是展示了如何将文件上传到 S3。 Hadoop 使这变得困难,因为文件首先保存到磁盘。我想直接从 ParquetWriter 转到 s3。
  • @markthegrea Hadoop 使用 HDFS 来模拟 POSIX 文件系统行为,而 S3 是对象存储,而不是文件系统 - 因此要直接写入 s3,您必须配置集群 HDFS 配置以与之同步S3-但同样,您的 Hadoop 作业仍将利用 Hadoop API 的 HDFS 实现,并且您将在集群磁盘空间上进行写入。如果您使用 AWS EMR(已设置此同步),您可以将对象从 HDFS 推送到 s3。在避免写入 HDFS 的情况下,您的用例是什么?
  • 我们只是将数据转换为镶木地板。如上所示,parquetWriter 只写入本地文件系统。我们需要在不涉及 hadoop 的情况下写入 s3。
  • @markthegrea 如果没有一些包装器,您不能直接写入 S3,该包装器通过创建一些基于 S3 路径的 sudo 文件系统树来修改 Hadoop API HDFS 实现以使用对象存储(s3)(m4gic 提供链接)。 Hadoop 的想法是使用 HDFS,因为在 map reduce 期间,您可以在本地检索存储的块,并且 shuffle 是内部的 - YARN 可以管理它。我想,我想了解的是为什么要努力使用从长远来看可能更难维护的包装器?除非磁盘空间是问题?
猜你喜欢
  • 2020-12-21
  • 1970-01-01
  • 2016-09-27
  • 2021-05-11
  • 2010-10-28
  • 2013-01-13
  • 2016-08-07
  • 2016-05-11
  • 2018-04-27
相关资源
最近更新 更多