Spark结构化流kafka转换JSON没有架构(推断架构)

时间:2018-01-20 21:15:33

标签: apache-spark apache-kafka schema spark-structured-streaming

我读过Spark Structured Streaming不支持将Kafka消息作为JSON读取的模式推断。有没有办法像Spark Streaming那样检索模式:

val dataFrame = spark.read.json(rdd.map(_.value()))
dataFrame.printschema 

6 个答案:

答案 0 :(得分:8)

这是执行此操作的一种可能方法:

  1. 开始流式传输之前,请先从Kafka中获取一小部分数据

  2. 从小批量推断模式

  3. 开始使用提取的模式流式传输数据。

下面的伪代码说明了这种方法。

步骤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