我读过Spark Structured Streaming不支持将Kafka消息作为JSON读取的模式推断。有没有办法像Spark Streaming那样检索模式:
val dataFrame = spark.read.json(rdd.map(_.value()))
dataFrame.printschema
答案 0 :(得分:8)
这是执行此操作的一种可能方法:
开始流式传输之前,请先从Kafka中获取一小部分数据
从小批量推断模式
开始使用提取的模式流式传输数据。
下面的伪代码说明了这种方法。
步骤1:
从卡夫卡提取一小批(两条记录)
val smallBatch = spark.read.format("kafka")
.option("kafka.bootstrap.servers", "node:9092")
.option("subscribe", "topicName")
.option("startingOffsets", "earliest")
.option("endingOffsets", """{"topicName":{"0":2}}""")
.load()
.selectExpr("CAST(value AS STRING) as STRING").as[String].toDF()
步骤2: 将小批量写入文件:
smallBatch.write.mode("overwrite").format("text").save("/batch")
此命令将小批量写入hdfs目录/ batch。它创建的文件的名称为part-xyz *。因此,您首先需要使用hadoop FileSystem命令重命名文件(请参阅org.apache.hadoop.fs._和org.apache.hadoop.conf.Configuration,这里是一个示例https://stackoverflow.com/a/41990859),然后将文件读取为json :
val smallBatchSchema = spark.read.json("/batch/batchName.txt").schema
在这里,batchName.txt是文件的新名称,smallBatchSchema包含从小批量中推断出的架构。
最后,您可以按以下方式流传输数据(第3步):
val inputDf = spark.readStream.format("kafka")
.option("kafka.bootstrap.servers", "node:9092")
.option("subscribe", "topicName")
.option("startingOffsets", "earliest")
.load()
val dataDf = inputDf.selectExpr("CAST(value AS STRING) as json")
.select( from_json($"json", schema=smallBatchSchema).as("data"))
.select("data.*")
希望这会有所帮助!
答案 1 :(得分:5)
可能使用此构造:
myStream = spark.readStream.schema(spark.read.json("my_sample_json_file_as_schema.json").schema).json("my_json_file")..
这怎么可能?好吧,因为spark.read.json(“..”)。schema只返回一个想要的推断模式,你可以使用这个返回的模式作为spark.readStream的强制模式参数的参数
我所做的是将单行sample-json指定为用于推断模式内容的输入,这样就不会占用内存。如果您的数据发生变化,只需更新您的sample-json。
我花了一段时间才弄清楚(手工构建StructTypes和StructFields是痛苦的......),因此我会为所有的赞成票感到高兴: - )
答案 2 :(得分:2)
这是不可能的。 Spark Streaming在spark.sql.streaming.schemaInference
设置为true
:
默认情况下,基于文件的源的结构化流要求您指定架构,而不是依靠Spark自动推断它。此限制可确保即使在出现故障的情况下,也将使用一致的架构进行流式查询。对于临时用例,您可以通过将spark.sql.streaming.schemaInference设置为true来重新启用模式推断。
但它不能用于从Kafka消息中提取JSON,DataFrameReader.json
不支持将流Datasets
作为参数。
您必须手动提供架构How to read records in JSON format from Kafka using Structured Streaming?
答案 3 :(得分:2)
将Arnon's解决方案带入下一步(因为它已经在spark的新版本中被弃用,并且需要针对类型转换迭代整个数据框)
spark.read.json(df.as[String])
无论如何,就目前而言,它仍处于试验阶段。
答案 4 :(得分:1)
可以将JSON转换为DataFrame而无需手动输入架构,如果这是您想要问的话。
最近我遇到了一种情况,我通过Kafka接收了大量长嵌套的JSON数据包,并且手动输入模式既麻烦又容易出错。
通过一小部分数据和一些技巧,您可以按如下方式向Spark2 +提供架构:
val jsonstr = """ copy paste a representative sample of data here"""
val jsondf = spark.read.json(Seq(jsonstr).toDS) //jsondf.schema has the nested json structure we need
val event = spark.readStream.format..option...load() //configure your source
val eventWithSchema = event.select($"value" cast "string" as "json").select(from_json($"json", jsondf.schema) as "data").select("data.*")
现在,您可以像使用Direct Streaming一样使用此val执行任何操作。创建临时视图,运行SQL查询,等等..
答案 5 :(得分:0)
我能够使用 foreachBatch sink 推断结构化流作业中的架构(无需写入/读取文件):
// define helper function to process each micro-batch
def processBatch( batchDF:DataFrame, batchId:Long ) : Unit = {
batchDF.persist()
// infer the schema
val schema = spark.read.json(batchDF.select(col("value")).as[String]).schema
// estimate size of the file to be created
val estimateBytes: Float = (spark.sessionState.executePlan(batchDF.queryExecution.logical)
.optimizedPlan.stats.sizeInBytes.toLong / totalCompressionRatioEstimate)
val numberOfFiles: Int = (estimateBytes.toFloat/appConfig("maxFileSize").toInt).ceil.toInt
batchDF.select(
from_json(col("value"), schema).alias("value")
)
.select(
col("value"),
(col("value.request_date")/1000).cast(TimestampType).alias("timestamp")
)
.select(
col("value.*"),
date_format(col("timestamp"), "yyyy").alias("year"),
date_format(col("timestamp"), "MM").alias("month"),
date_format(col("timestamp"), "dd").alias("day"),
date_format(col("timestamp"), "hh").alias("hour"),
date_format(col("timestamp"), "mm").alias("minute"),
unix_timestamp().as("upload_ts")
)
.repartition(numberOfFiles)
.write
// columnar file format
.format(appConfig("outputType"))
.mode("append")
.partitionBy("account_id", "year", "month", "day", "hour", "minute")
.option("path", appConfig("outputLocation"))
.save()
batchDF.unpersist()
}
// read Kafka stream
spark.readStream
.format("kafka")
.options(kafkaConfig)
.option("failOnDataLoss", "false")
.load()
.select(col("value").cast(StringType).alias("value"))
.repartition(appConfig("repartitionCount").toInt) // default: 1
.writeStream.
foreachBatch(processBatch _)
.option("checkpointLocation", appConfig("checkpointLocation"))
.trigger(Trigger.ProcessingTime(s"${appConfig("triggerSeconds")} seconds")) // default : 60 (seconds)
.start().awaitTermination()
不过,我是 Spark 和 Scala 的新手。如果有更多经验的人审查了上述代码并让我知道一切是否正常,我将不胜感激。 @jacek