Google cloud dataflow 通过数据流到BigQuery从Google云存储读取大型gzip JSON文件

Google cloud dataflow 通过数据流到BigQuery从Google云存储读取大型gzip JSON文件,google-cloud-dataflow,Google Cloud Dataflow,我试图从Google云存储(GCS)读取大约90个Gzip JSON日志文件,每个文件大约2GB大(10GB未压缩),解析它们,并通过Google云数据流(GCDF)将它们写入到BigQuery(BQ)的一个日期分区表中 每个文件保存7天的数据,整个日期范围约为2年(730天,正在计算)。我当前的管道如下所示: p.apply("Read logfile", TextIO.Read.from(bucket)) .apply("Repartition", Repartition.of()) .

我试图从Google云存储(GCS)读取大约90个Gzip JSON日志文件,每个文件大约2GB大(10GB未压缩),解析它们,并通过Google云数据流(GCDF)将它们写入到BigQuery(BQ)的一个日期分区表中

每个文件保存7天的数据,整个日期范围约为2年(730天,正在计算)。我当前的管道如下所示:

p.apply("Read logfile", TextIO.Read.from(bucket))
 .apply("Repartition", Repartition.of())
 .apply("Parse JSON", ParDo.of(new JacksonDeserializer()))
 .apply("Extract and attach timestamp", ParDo.of(new ExtractTimestamps()))
 .apply("Format output to TableRow", ParDo.of(new TableRowConverter()))
 .apply("Window into partitions", Window.into(new TablePartWindowFun()))
 .apply("Write to BigQuery", BigQueryIO.Write
         .to(new DayPartitionFunc("someproject:somedataset", tableName))
         .withSchema(TableRowConverter.getSchema())
         .withCreateDisposition(BigQueryIO.Write.CreateDisposition.CREATE_IF_NEEDED)
         .withWriteDisposition(BigQueryIO.Write.WriteDisposition.WRITE_APPEND));
重新分区是我在尝试创建管道时内置的东西,我尝试过在有和没有它的情况下运行管道。解析JSON通过Jackon ObjectMapper和建议的相应类工作。TablePartWindowFun取自,用于为PCollection中的每个条目分配一个分区

管道适用于较小的文件,但不太多,但对于我的真实数据集,管道中断。我选择了足够大的机器类型,并尝试设置最大工作人数,以及使用自动缩放多达100台n1-highmem-16机器。我尝试了流式处理和批处理模式,并将每个工作线程的GB值从250 GB更改为1200 GB

目前我能想到的可能解决办法是:

  • 解压缩GCS上的所有文件,从而启用工作人员之间的动态工作拆分,因为不可能利用GCS的
  • 在循环中构建“许多”并行管道,每个管道只处理90个文件的子集
  • 在我看来,选项2就像是围绕一个框架进行编程,还有其他解决方案吗

    附录:

    在以批处理模式读取gzip JSON文件(最多100个工作线程)(n1-highmem-4类型)后进行重新分区,管道将使用12个工作线程运行约一小时,并完成读取和第一阶段的重新分区。然后,它可以扩展到100个工人,并处理重新分区的PCollection。完成后,图形如下所示:

    p.apply("Read logfile", TextIO.Read.from(bucket))
     .apply("Repartition", Repartition.of())
     .apply("Parse JSON", ParDo.of(new JacksonDeserializer()))
     .apply("Extract and attach timestamp", ParDo.of(new ExtractTimestamps()))
     .apply("Format output to TableRow", ParDo.of(new TableRowConverter()))
     .apply("Window into partitions", Window.into(new TablePartWindowFun()))
     .apply("Write to BigQuery", BigQueryIO.Write
             .to(new DayPartitionFunc("someproject:somedataset", tableName))
             .withSchema(TableRowConverter.getSchema())
             .withCreateDisposition(BigQueryIO.Write.CreateDisposition.CREATE_IF_NEEDED)
             .withWriteDisposition(BigQueryIO.Write.WriteDisposition.WRITE_APPEND));
    

    有趣的是,当达到这个阶段时,首先它处理高达150万个元素/秒,然后进度下降到0。图中GroupByKey步骤的OutputCollection大小先升后降,从大约3亿到0(总共约有18亿个元素)。就像是丢弃了什么东西。另外,
    ExpandIterable
    ParDo(流式写入)
    最后的运行时为0。图中显示了在“向后”运行之前的轻微变化。 在工作人员的日志中,我看到执行来自
    com.google.api.client.http.HttpTransport
    记录器的请求时抛出了一些
    异常,但我在Stackdriver中找不到更多信息

    如果在读取管道后不进行重新分区,则在完全相同的步骤中使用内存不足错误的
    n1-highmem-2
    实例会失败(在
    GroupByKey
    之后的所有内容)-使用更大的实例类型会导致以下异常:

    java.util.concurrent.ExecutionException: java.io.IOException: 
    CANCELLED: Received RST_STREAM with error code 8 dataflow-...-harness-5l3s 
    talking to frontendpipeline-..-harness-pc98:12346
    

    在运行管道时,通过将--numWorkers设置为更高的值,尝试为管道分配更多资源可能是值得的。这是在线“管道故障排除”中讨论的可能解决方案之一,位于“常见错误和行动方案”子章节

    多亏了谷歌云数据流团队的丹和他提供的例子,我才能够解决这个问题。我所做的唯一改变是:

    • 以175=(25周)的大数据块在几天内循环,一个接一个地运行管道,以避免系统崩溃。在循环中,确保重新处理上一次迭代的最后文件,并以与基础数据相同的速度向前移动
      startDate
      。当使用
      WriteDisposition.WRITE\u TRUNCATE
      时,块末尾的不完整天数将以这种方式用正确的完整数据覆盖

    • 在读取gzip文件后,使用上面提到的重新分区/重新洗牌转换,以加快过程并允许更平滑的自动缩放

    • 使用DateTime而不是Instant类型,因为我的数据不是UTC格式

    更新(Apache Beam 2.0):
    随着ApacheBeam2.0的发布,解决方案变得更加简单。现在支持切分BigQuery输出表。

    “但我的真实数据集会中断”-到底发生了什么?您遇到了什么错误?我添加了一个错误示例。我添加了另一个没有重新分区的示例。Stephen管道在直接写入表时,而不是使用
    to(new DayPartitionFunc(“someproject:somedataset”,tableName))
    ?在批处理管道中,基于窗口的分区的使用目前是实验性的(Javadoc说不支持批处理)。它可能无法扩展,也无法直接写入特定的表。如果我针对一个特定的表,它可以正常工作,但最终会得到一个巨大的表(1.9 TB),考虑到我有730多天的时间,通过查询将其细分为分区的成本非常高。我已经尝试过这种方法,即使使用最大的机器类型也会失败。