【问题标题】:Unsupported operation exception from spark: Schema for type org.apache.spark.sql.types.DataType is not supportedspark 不支持的操作异常:不支持 org.apache.spark.sql.types.DataType 类型的架构
【发布时间】:2021-11-21 18:56:58
【问题描述】:

Spark 流式传输:

我收到一个包含两列的数据框。第一列是string 类型,包含一个json 字符串,第二列由schema 组成,每个value(第一列)。

Batch: 0
-------------------------------------------
+--------------------+--------------------+
|               value|              schema|
+--------------------+--------------------+
|{"event_time_abc...|`event_time_abc...|
+--------------------+--------------------+

该表存储在val input(非可变变量)中。我正在使用DataType.fromDDL 函数通过以下方式将string 类型转换为json DataFrame:

val out=  input.select(from_json(col("value").cast("string"),ddl(col("schema"))))

ddl 是一个预定义函数,DataType.from_ddl(_:String):DataType 在 spark(scala) 中,但我已经注册了它,以便我可以在整个列上使用它,而不是仅在字符串上使用它。我是通过以下方式完成的:

val ddl:UserDefinedFunction = udf(DataType.fromDDL(_:String):DataType)

这是input 表的列、值和架构的最终转换。

val out =  input.select(from_json(col("value").cast("string"),ddl(col("schema"))))

但是,我在这一行的注册中遇到了异常:

val ddl:UserDefinedFunction = udf(DataType.fromDDL(_:String):DataType)

错误是:

java.lang.UnsupportedOperationException: Schema for type org.apache.spark.sql.types.DataType is not supported
 

如果我使用:

val out =  input.select(from_json(col("value").cast("string"),DataType.fromDDL("`" + "event_time_human2"+"`" +" STRING")).alias("value"))

然后它可以工作,但正如你所见,我只在函数DataType.fromDDL(_:String):DataType 中使用string(手动输入来自schema 列)。

那么如何在不注册的情况下将此功能应用于整个列,或者有没有其他方法可以注册该功能?

编辑:from_json 函数的第一个参数需要一个列,而第二个参数需要一个 schema 而不是一个列。因此,我想需要一种手动方法来解析每个 value 字段和每个 schema 字段。经过一番调查,我发现 DataFrames 不支持 DataType。


自从在这个问题上设置了赏金。我想提供有关数据和架构的其他信息。模式在 DDL(字符串类型)中定义,可以使用 from_DDL 函数解析。该值是简单的json 字符串,将使用我们使用from_DDL 函数派生的架构进行解析。

基本思想是每个值都有自己的模式,需要用相应的模式进行解析。应该在存储结果的位置创建一个新列。

数据: 这是数据的一个示例:

值 = {"event_time_human2":"09:45:00 +0200 09/27/2021"}

schema = "`event_time_human2` STRING"

不需要转换为正确的时间格式。只要一个字符串就可以了。

它位于streaming context。因此,并非所有方法都有效。

【问题讨论】:

  • col("schema") 实际上只是一个命名的 Column 对象,而不是那个单元格的选定字符串内容
  • @OneCricketeer ddl 由我注册,它需要一个字符串,但注册后 spark 传递一个列而不是字符串。
  • “取一个字符串”,好吧,但是你给它一个类型未定义的列对象。 input 的架构是什么?
  • 也许尝试定义一个接受两个参数的UDF,该参数接受两个列并在其上运行from_json
  • @OneCricketeer 我试过当我传递两列时,from_json 函数的输出是一个再次未定义的列。所以我不能传回结果。

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


【解决方案1】:

在运行时之前应用和验证模式,即在执行器上执行 Spark 代码之前。解析的模式必须是执行计划的一部分,因此模式解析直到现在还不能按照您的预期动态执行。这就是您看到异常的原因: java.lang.UnsupportedOperationException: Schema for type org.apache.spark.sql.types.DataType is not supported 仅适用于 UDF。因此,这意味着 DataType.fromDDL 应仅在驱动程序代码中使用,而不应在运行时/执行程序代码中使用,后者是 UDF 函数中的代码。在 UDF 函数内,Spark 已经应用您在驱动程序端指定的模式执行了导入数据的转换。这就是您不能在 UDF 中直接使用 DataType.fromDDL 的原因,因为它本质上是无用的。以上所有意味着在 UDF 函数内部,我们只能使用原始的 Scala/Java 类型和 Spark API 提供的一些包装器,即 WrappedArray。

另一种方法是收集驱动程序上的所有模式。然后为每个模式创建一个包含对(模式、数据框)的映射。

请记住,向驱动程序收集数据是一项昂贵的操作,只有当您拥有合理数量的唯一模式(即最多数千个)时才有意义。此外,将这些模式应用于每个数据集需要在驱动程序中按顺序完成,这也非常昂贵,因此重要的是要认识到建议的解决方案只有在唯一模式数量有限的情况下才能有效工作。

到目前为止,您的代码可能如下所示:

import org.apache.spark.sql.functions.from_json
import org.apache.spark.sql.types.StructType

import spark.implicits._

val df = Seq(
  ("""{"event_time_human2":"09:45:00 +0200 09/27/2021", "name":"Pinelopi"}""", "`event_time_human2` STRING, name STRING"),
  ("""{"first_name":"Pin", "last_name":"Halen"}""", "first_name STRING, last_name STRING"),
  ("""{"code":993, "full_name":"Sofia Loren"}""", "code INT, full_name STRING")
).toDF("value", "schema")

val schemaDf = df.select("schema").distinct()

val dfBySchema = schemaDf.collect().map{ row =>
  val schemaValue = row.getString(0)
  val ddl = StructType.fromDDL(schemaValue)
  val filteredDf = df.where($"schema" === schemaValue)
                     .withColumn("value", from_json($"value", ddl))
  
  (schemaValue, filteredDf)
}.toMap

// Map(
//   `event_time_human2` STRING, name STRING -> [value: struct<event_time_human2: string, name: string>, schema: string], 
//   first_name STRING, last_name STRING -> [value: struct<first_name: string, last_name: string>, schema: string], 
//   code INT, full_name STRING -> [value: struct<code: int, full_name: string>, schema: string]
// )

说明:首先我们用schemaDf.collect() 收集每个独特的模式。然后我们遍历模式并根据当前模式过滤初始 df。我们还使用from_json 将当前字符串值列转换为特定架构。

注意,我们不能有一个具有不同数据类型的公共列,这就是我们为每个架构创建不同的 df 而不是最终的 df 的原因。

【讨论】:

  • 感谢您的回答。对此,我真的非常感激。如果它有效,我将检查流式上下文,然后我会接受你的回答。据我所知,每次新的微批次进入(流)时,我们都会再次收集不同的模式。是否可以只收集一次所有不同的模式(从第一批)并将它们保存在内存中。每个后续批次都将使用已经存在的模式进行测试,如果有异常,我们可以提取它对应的模式并解析它?
  • 我只是想让您知道您的代码在流模式下不​​起作用。我收到以下错误“必须使用 writeStream.start() 执行带有流源的查询”
  • 对于流媒体,您必须重新启动程序才能在驱动程序上收集所有模式,然后重新加载所有收集的模式。不幸的是,我不知道这里有任何替代方案。另外,请使用此特定信息更新您的问题,因为这是一个重要的细节
  • 这并不重要,但如果有任何解决方案适用于流,那就没问题了。
  • @KhanSaab 对于流媒体,您需要采用完全不同的方法。事实上,您根本不应该使用 Spark DDL 模式表示。一种解决方案是以不同的格式(即 Avro)存储模式,然后在 UDF 函数中解析验证 json 值。同样,这构成了与上述问题中描述的问题完全不同的问题。我认为写一个新问题是一个更好的主意,你应该在其中解释你想要实现的确切目标
猜你喜欢
  • 2017-03-23
  • 2018-02-26
  • 2021-05-12
  • 1970-01-01
  • 1970-01-01
  • 2017-06-26
  • 1970-01-01
  • 1970-01-01
  • 2021-08-17
相关资源
最近更新 更多