我正在使用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或>
中进行流源的联接/联合答案 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中的数据帧。