【问题标题】:Operating in parallel on a Spark Dataframe Rows在 Spark Dataframe Rows 上并行操作
【发布时间】:2020-08-01 22:50:30
【问题描述】:

环境:Scala、spark、结构化流、kafka

我有一个来自具有以下架构的 kafka 流的 DF

DF:

BATCH ID: 0
+-----------------------+-----+---------+------+
|                  value|topic|partition|offset|
+-----------------------+-----+---------+------+
|{"big and nested json"}|  A  |        0|     0|
|{"big and nested json"}|  B  |        0|     0|
+-----------------------+-----+---------+------+

我想使用 spark 并行处理每一行,并且我设法将它们拆分给我的执行者使用

DF.repartition(Number).foreach(row=> processRow(row))

我需要将值列中的值提取到它自己的数据框中来处理它。 我在使用 Dataframe 通用 Row 对象时遇到了困难..

有没有办法将每个执行程序中的单行转换为自己的数据帧(使用固定模式?)并在固定位置写入? 有没有更好的方法来解决我的问题?

编辑+澄清:

使用自 spark2.4 以来存在的 writeStream 功能的 forEachBatch 函数作为批处理来接收 DF

目前将 DF 拆分为 ROWS 使得行将平均拆分为我的所有执行程序,我想将单个 GenericRow 对象转换为 DataFrame,以便我可以使用我制作的函数进行处理

例如,我会将行发送到函数

processRow(row:row)

取值和主题,转回单行DF

+-----------------------+-----+
|                  value|topic|
+-----------------------+-----+
|{"big and nested json"}|  A  |
+-----------------------+-----+

用于进一步处理

【问题讨论】:

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


    【解决方案1】:

    我猜你一次消耗多个 kafka 数据。

    首先你需要为所有kafka主题准备schema,例如我在value列中使用了两个不同的JSON。

    scala> val df = Seq(("""{"name":"Srinivas"}""","A"),("""{"age":20}""","B")).toDF("value","topic")
    
    scala> df.show(false)
    +-------------------+-----+
    |value              |topic|
    +-------------------+-----+
    |{"name":"Srinivas"}|A    |
    |{"age":20}         |B    |
    +-------------------+-----+
    
    scala> import org.apache.spark.sql.types._
    

    主题 A 的架构

    scala> val topicASchema = DataType.fromJson("""{"type":"struct","fields":[{"name":"name","type":"string","nullable":true,"metadata":{}}]}""").asInstanceOf[StructType]
    

    主题 B 的架构

    scala> val topicBSchema = DataType.fromJson("""{"type":"struct","fields":[{"name":"age","type":"long","nullable":true,"metadata":{}}]}""").asInstanceOf[StructType]
    

    结合主题及其架构。

    scala> val topicSchema = Seq(("A",topicASchema),("B",topicBSchema)) // Adding Topic & Its Schema.
    

    处理数据帧

    scala> topicSchema
    .par
    .map(d => df.filter($"topic" === d._1).withColumn("value",from_json($"value",d._2)))
    .foreach(_.show(false)) // Using .par & filtering dataframe based on topic & then applying schema to value column.
    +----------+-----+
    |value     |topic|
    +----------+-----+
    |[Srinivas]|A    |
    +----------+-----+
    
    +-----+-----+
    |value|topic|
    +-----+-----+
    |[20] |B    |
    +-----+-----+
    

    写入 hdfs

    scala> topicSchema
    .par
    .map(d => df.filter($"topic" === d._1).withColumn("value",from_json($"value",d._2)).write.format("json").save(s"/tmp/kafka_data/${d._1}"))
    

    存储在 hdfs 中的最终数据

    scala> import sys.process._
    import sys.process._
    
    scala> "tree /tmp/kafka_data".!
    /tmp/kafka_data
    ├── A
    │   ├── part-00000-1e854106-49de-44b3-ab18-6c98a126c8ca-c000.json
    │   └── _SUCCESS
    └── B
        ├── part-00000-1bd51ad7-cfb6-4187-a374-4e2d4ce9cc50-c000.json
        └── _SUCCESS
    
    2 directories, 4 files
    

    【讨论】:

    • 我之前没有提到这一点,我使用的 DF 在 writeStream 下的 ForeachBatch 中,所以这个确切的灵魂不适合,我在问题中添加了更多细节。
    • 你能解释为什么它不适合你的解决方案吗?
    • 首先,我在尝试您的代码时犯了一个错误,我的错,它可以工作。但是它不适合,因为我想将行分成不同的数据框,因为 json 内部是指示我将在何处写入它们的数据。您的解决方案将它们分开,但它们在同一个 df 中,我想将它们分开并将它们作为单独的数据帧发送到函数!
    • 也许您可以使用过滤器来拆分数据,然后将其传递给您的函数。
    • 上述解决方案也是基于topic 列进行过滤。它会将数据拆分为多个数据帧,然后应用函数单个数据帧。
    【解决方案2】:

    在这种情况下,更适合使用.map 而不是.foreach。原因是map 返回一个新数据集,而foreach 只是一个函数,不返回任何内容。

    可以帮助您的另一件事是解析位于 JSON 中的架构。

    我最近有类似的要求。 我的 JSON 对象对于主题 AB 都有一个“相似”模式。如果您不是这种情况,您可能需要在下面的解决方案中通过按主题对它们进行分组来创建多个dataframes

    val sanitiseJson: String => String = value => value
      .replace("\\\"", "\"")
      .replace("\\\\", "\\")
      .replace("\n", "")
      .replace("\"{", "{")
      .replace("}\"", "}")
    
    val parsed = df.toJSON
      .map(sanitiseJson)
    

    这会给你类似的东西:

    {
        "value": { ... },
        "topic": "A"
    }
    

    然后你可以将它传递给一个新的read 函数:

    var dfWithSchema = spark.read.json(parsed)
    

    此时您将访问嵌套 JSON 中的值:

    dfWithSchema.select($"value.propertyInJson")
    

    如果需要,您可以对sanitiseJson 进行一些优化。

    【讨论】:

    • 这并不能完全解决我的问题,将我的 json 值转换为我使用 from_json 的 DF,我需要将 Row 对象转换回 DF
    • 我很难理解你想要做什么。 Row 对象是 Dataframe 的定义部分。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-12-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-08
    • 2020-11-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多