使用UDF映射有条件地创建新列时java.io.NotSerializableException:org.apache.spark.sql.Column

时间:2018-03-15 06:33:11

标签: scala apache-spark apache-spark-sql user-defined-functions

我有一个带有startTime和一些要素向量的设备ID数据,需要根据hourweekday_hour进行合并。样本数据如下:

+-----+-------------------+--------------------+
|hh_id|          startTime|                hash|
+-----+-------------------+--------------------+
|dev01|2016-10-10 00:01:04|(1048576,[121964,...|
|dev02|2016-10-10 00:17:45|(1048576,[121964,...|
|dev01|2016-10-10 00:18:01|(1048576,[121964,...|
|dev10|2016-10-10 00:19:48|(1048576,[121964,...|
|dev05|2016-10-10 00:20:00|(1048576,[121964,...|
|dev08|2016-10-10 00:45:13|(1048576,[121964,...|
|dev05|2016-10-10 00:56:25|(1048576,[121964,...|

这些功能基本上是SparseVectors,它们由自定义函数合并。当我尝试按以下方式创建列时:

val columnMap = Map("hour" -> hour($"startTime"), "weekday_hour" -> getWeekdayHourUDF($"startTime"))
val grouping = "hour"
val newDF = oldDF.withColumn("dt_key", columnMap(grouping))

我得到java.io.NotSerializableException。完整的堆栈跟踪如下:

Caused by: java.io.NotSerializableException: org.apache.spark.sql.Column
Serialization stack:
    - object not serializable (class: org.apache.spark.sql.Column, value: hour(startTime))
    - field (class: scala.collection.immutable.Map$Map3, name: value1, type: class java.lang.Object)
    - object (class scala.collection.immutable.Map$Map3, Map(hour -> hour(startTime), weekday_hour -> UDF(startTime), none -> 0))
    - field (class: linef03f4aaf3a1c4f109fce271f7b5b1e30104.$read$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw, name: groupingColumnMap, type: interface scala.collection.immutable.Map)
    - object (class linef03f4aaf3a1c4f109fce271f7b5b1e30104.$read$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw, linef03f4aaf3a1c4f109fce271f7b5b1e30104.$read$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw@4f1f9a63)
    - field (class: linef03f4aaf3a1c4f109fce271f7b5b1e30104.$read$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw, name: $iw, type: class linef03f4aaf3a1c4f109fce271f7b5b1e30104.$read$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw)
    - object (class linef03f4aaf3a1c4f109fce271f7b5b1e30104.$read$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw, linef03f4aaf3a1c4f109fce271f7b5b1e30104.$read$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw@207d6d1e)

但是当我尝试执行相同的逻辑而不显式创建列时,使用if-else,我不会遇到任何此类错误。

val newDF = if(groupingKey == "hour") {
  oldDF.withColumn("dt_key", hour($"startTime")
} else {
  oldDF.withColumn("dt_key", getWeekdayHourUDF($"startTime")
}

使用Map-way来实现它会非常方便,因为可能有更多类型的密钥提取方法。请帮我弄清楚为什么会引起这个问题。

2 个答案:

答案 0 :(得分:0)

内置功能时

您可以使用{ "status": "Active" } 内置函数

来达到您的要求
when

udf功能

或者,您可以创建val groupingKey = //"hour" or "weekday_hour" import org.apache.spark.sql.functions._ df.withColumn("dt_key", when(lit(groupingKey) === "hour", hour($"startTime")).otherwise(when(lit(groupingKey) === "weekday_hour", getWeekdayHourUDF($"startTime")).otherwise(lit(0)))).show(false) 函数以创建地图列

udf

并将其用作

import org.apache.spark.sql.functions._
def mapUdf = udf((hour: Int, weekdayhour: Int, groupingKey: String) => if(groupByKey.equalsIgnoreCase("hour")) hour else if(groupByKey.equalsIgnoreCase("weekday_hour")) weekdayhour else 0)

我希望答案很有帮助

答案 1 :(得分:0)

也许有点晚了,但我在 Spark 2.4.6 并且无法重现该问题。我猜测代码为多个键调用 columnMap。如果您提供一个易于重现的示例,包括数据(1 行数据集就足够了),它会有所帮助。但是,正如堆栈跟踪所说,Column 类确实不是Serializable,我将根据我目前的理解尝试详细说明。

TLDR;避免这种情况的一种简单方法是将 val 转换为 def


我相信很清楚为什么用 when 案例或 UDF 表达同样的事情是有效的。

第一次尝试:这样的事情可能不起作用的原因是因为 (a) Column 类是不可序列化的(我认为这是一个有意识的设计选择,因为它的预期是Spark API 中的角色),并且 (b) 表达式中没有任何内容

oldDF.withColumn("dt_key", columnMap(grouping))

这告诉 Spark Column 的第二个参数的实际具体 withColumn 是什么,这意味着具体的 Map[String, Column] 对象需要通过网络发送给执行程序,当引发这样的异常时。

第二次尝试:第二次尝试成功的原因是关于定义 groupingKey 所需的此 DataFrame 参数的相同决定可以完全发生在驱动程序上。< /p>


考虑使用 DataFrame API 作为查询构建器的 Spark 代码,或者保存执行计划的东西,而不是数据本身,会有所帮助。一旦您对其调用操作(writeshowcount 等),Spark 会生成将任务发送给执行程序的代码。那时,实现 DataFrame/Dataset 所需的所有信息必须已经在查询计划中正确编码,或者需要可序列化,以便可以通过网络发送。

def 通常可以解决此类问题,因为

def columnMap: Map[String, Column] = Map("a" -> hour($"startTime"), "weekday_hour" -> UDF($"startTime"))

不是具体的 Map 对象本身,而是在每次调用时创建一个新的 Map[String, Column]这个Map

Thisthis 似乎是有关该主题的好资源。我承认我明白为什么使用 Function like

val columnMap = () => Map("a" -> hour($"startTime"), "b" -> UDF($"startTime"))

然后 columnMap()("a") 会起作用,因为反编译的字节码显示 scala.Function 被定义为 Serializable 的具体实例,但我不明白为什么 {{1}有效,因为这对他们来说似乎并非如此。无论如何,我希望这会有所帮助。