Spark的结构化流媒体是否有办法为DataStreamWriter
的查询计划添加最终操作?我试图从流数据源读取,以某种方式丰富数据,然后以镶木地板格式写回分区的外部表(假设Hive)。写操作工作正常,为我分区目录中的数据,但我似乎无法弄清楚如何在将数据写入磁盘之后另外运行MSCK REPAIR TABLE
或ALTER 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:也许使用像 - 使用ForeachWriter不会导致在批处理完成后调用.foreach(new ForeachWriter[Row])
之类的东西并传递FileStreamSink
或类似的东西会起作用(使用def close()
来运行外部查询),但我没有仔细研究它以便很好地掌握它。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
})
我在文档中没有看到任何内容,希望我没有错过任何内容。提前谢谢。
答案 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
}
}
}