Spark结构化流-UNION两个或多个流源

时间:2019-07-01 20:13:41

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

我正在使用spark 2.3.2,并在对来自Kafka的2个或更多流源进行合并时遇到问题。这些都是Kafka的流媒体源,我已经对其进行了转换并将其存储在Dataframes中。

理想情况下,我想将这个UNIONed数据帧的结果以木地板格式存储在HDFS中,或者甚至可能存储回​​Kafka中。最终目标是以尽可能低的延迟存储这些合并的事件。

val finalDF = flatDF1
      .union(flatDF2)
      .union(flatDF3)

val query = finalDF.writeStream
      .format("parquet")
      .outputMode("append")
      .option("path", hdfsLocation)
      .option("checkpointLocation", checkpointLocation)
      .option("failOnDataLoss", false)
      .start()

    query.awaitTermination()

当执行writeStream控制台而不是实木复合地板时,我得到了预期的结果,但是上面的示例导致断言失败。

Caused by: java.lang.AssertionError: assertion failed
    at scala.Predef$.assert(Predef.scala:156)
    at org.apache.spark.sql.execution.streaming.OffsetSeq.toStreamProgress(OffsetSeq.scala:42)
    at org.apache.spark.sql.execution.streaming.MicroBatchExecution.org$apache$spark$sql$execution$streaming$MicroBatchExecution$$populateStartOffsets(MicroBatchExecution.scala:185)
    at org.apache.spark.sql.execution.streaming.MicroBatchExecution$$anonfun$runActivatedStream$1$$anonfun$apply$mcZ$sp$1.apply$mcV$sp(MicroBatchExecution.scala:124)
    at org.apache.spark.sql.execution.streaming.MicroBatchExecution$$anonfun$runActivatedStream$1$$anonfun$apply$mcZ$sp$1.apply(MicroBatchExecution.scala:121)
    at org.apache.spark.sql.execution.streaming.MicroBatchExecution$$anonfun$runActivatedStream$1$$anonfun$apply$mcZ$sp$1.apply(MicroBatchExecution.scala:121)

这是失败的类和断言:

case class OffsetSeq(offsets: Seq[Option[Offset]], metadata: Option[OffsetSeqMetadata] = None) {

assert(sources.size == offsets.size)

这是因为检查点仅存储数据帧之一的偏移量吗?通过查看Spark结构化流文档,似乎可以在Spark 2.2或>

中进行流源的联接/联合

1 个答案:

答案 0 :(得分:0)

首先,请定义案例类OffsetSeq与数据框的并集与代码之间的关系。

接下来,执行此联合然后使用writestream写入Kafka时,检查点是一个真正的问题。由于联合操作的缘故,分成多个写流(每个写流都有自己的检查点)会混淆批处理ID。将相同的writestream与数据帧联合一起使用时,检查点失败,因为检查点似乎在联合之前查找所有生成数据帧的模型,并且无法区分什么行/记录来自什么数据框架/模型。

要从结构化sql流联合数据帧写入Kafka,最好将writestream与foreach和ForEachWriter一起使用,包括process方法中的Kafka Producer。无需检查点;该应用程序只是使用临时检查点文件,这些文件将在适当的时候设置为删除-在会话构建器中将“ forceDeleteTempCheckpointLocation”设置为true。

无论如何,我刚刚设置了Scala代码以联合任意数量的流数据帧,然后将其写入Kafka Producer。一旦将所有Kafka Producer代码放入ForEachWriter处理方法中,以便Spark可以对其进行序列化,就可以正常工作。

val output = dataFrameModelArray.reduce(_ union _)
val stream: StreamingQuery = output
  .writeStream.foreach(new ForeachWriter[Row] {

    def open(partitionId: Long, version: Long): Boolean = {
      true
    }

    def process(row: Row): Unit = {
      val producer: KafkaProducer[String, String] = new KafkaProducer[String, String](props)
      val record = new ProducerRecord[String, String](producerTopic, row.getString(0), row.getString(1))
      producer.send(record)
    }

    def close(errorOrNull: Throwable): Unit = {
    }
  }
).start()

如果需要,可以在处理方法中添加更多逻辑。

请注意,在进行联合之前,所有要联合的数据帧均已转换为键,值字符串列。值是要通过Kafka Producer发送的消息数据的json字符串。这对于在尝试进行联合之前写信也很重要。

svcModel.transform(query)
    .select($"key", $"uuid", $"currentTime", $"label", $"rawPrediction", $"prediction")
    .selectExpr("key", "to_json(struct(*)) AS value")
    .selectExpr("CAST(key AS STRING)", "CAST(value AS STRING)")

其中svcModel是dataFrameModelArray中的数据帧。