外部表上的Spark结构化流运行最终操作(MSCK REPAIR)

时间:2017-08-24 06:23:49

标签: scala apache-spark parquet spark-structured-streaming

Spark的结构化流媒体是否有办法为DataStreamWriter的查询计划添加最终操作?我试图从流数据源读取,以某种方式丰富数据,然后以镶木地板格式写回分区的外部表(假设Hive)。写操作工作正常,为我分区目录中的数据,但我似乎无法弄清楚如何在将数据写入磁盘之后另外运行MSCK REPAIR TABLEALTER TABLE ADD PARTITION操作可能已创建的分区。

为简单起见,请以下面的Scala代码为例:

SparkSession
  .builder()
  .appName("some name")
  .enableHiveSupport()
  .getOrCreate()
  .readStream
  .format("text")
  .load("/path/from/somewhere")
  // additional transformations
  .writeStream
  .format("parquet")
  .partitionBy("some_column")
  .start("/path/to/somewhere")
   <--------------------  something I can place here for an additional operation?
  .awaitTermination()

潜在的解决方法?:

1:也许使用像.foreach(new ForeachWriter[Row])之类的东西并传递FileStreamSink或类似的东西会起作用(使用def close()来运行外部查询),但我没有仔细研究它以便很好地掌握它。 - 使用ForeachWriter不会导致在批处理完成后调用close()方法。

2:分流。以下内容:

val stream = SparkSession
  .builder()
  .appName("some name")
  .enableHiveSupport()
  .getOrCreate()
  .readStream
  .format("text")
  .load("/path/from/somewhere")
  // additional transformations

stream
  .writeStream
  .format("parquet")
  .partitionBy("some_column")
  .start("/path/to/somewhere")
  .awaitTermination()

stream
  .map(getPartitionName).distinct
  .map { partition =>

    // Run query here

    partition
  }
  .writeStream
  .start()
  .awaitTermination()

这里的问题是确保第一次操作在第二次操作之前完成。

3:为已完成的批次命名查询并附加监听器,手动添加所有分区。有点浪费,但可能有可行吗?

...
stream
  .writeStream
  .queryName("SomeName")
...


spark.streams.addListener(new StreamingQueryListener() {

  override def onQueryStarted(event: StreamingQueryListener.QueryStartedEvent): Unit = Unit
  override def onQueryProgress(event: QueryProgressEvent): Unit = {
    if (event.progress.name == "SomeName") {
      // search through files in filesystem and add partitions

      fileSystem.listDir("/path/to/directory").foreach { partition =>
        // run "ALTER TABLE ADD PARTITION $partition"
      }
    }
  }
  override def onQueryTerminated(event: StreamingQueryListener.QueryTerminatedEvent): Unit = Unit
})

我在文档中没有看到任何内容,希望我没有错过任何内容。提前谢谢。

1 个答案:

答案 0 :(得分:0)

使用StreamingQueryListener作品,虽然我不确定这是好/坏做法。

我实现了以下内容:

spark.streams.addListener(new StreamingQueryListener() {

  val client = new Client()

  override def onQueryStarted(event: StreamingQueryListener.QueryStartedEvent): Unit = Unit
  override def onQueryTerminated(event: StreamingQueryListener.QueryTerminatedEvent): Unit = Unit
  override def onQueryProgress(event: QueryProgressEvent): Unit = {
    if (event.progress.numInputRows > 0 && event.progress.sink.description.startsWith("FileSink") && event.progress.sink.description.contains("/path/to/write/directory")) {
      client.sql(s"MSCK REPAIR TABLE $db.$table")
    }
  }
})

如果您碰巧有基于时间的分区,只要您打算根据now()创建分区,这就可以正常工作:

spark.streams.addListener(new StreamingQueryListener() {

  val client = new Client()
  var lastPartition: String = ""
  val dateTimeFormat: String = "yyyy-MM-dd"

  override def onQueryStarted...
  override onQueryTerminated...
  override def onQueryProgress(event: QueryProgressEvent): Unit = {
    if (event.progress.numInputRows > 0 && event.progress.sink.description.startsWith("FileSink[s3") && event.progress.sink.description.contains("/path/to/write/directory")) {

      val newPartition = new DateTime().toString(dateTimeFormat)

      if (newPartition != lastPartition) {
        client.sql(s"ALTER TABLE $db.$table ADD IF NOT EXISTS PARTITION ($partitionColumn='$newPartition')")
        lastPartition = newPartition
    }
  }
}