为了测试spark中的序列化异常,我以两种方式编写了一个任务 第一种方式:
package examples
import org.apache.spark.SparkConf
import org.apache.spark.SparkContext
object dd {
def main(args: Array[String]):Unit = {
val sparkConf = new SparkConf
val sc = new SparkContext(sparkConf)
val data = List(1,2,3,4,5)
val rdd = sc.makeRDD(data)
val result = rdd.map(elem => {
funcs.func_1(elem)
})
println(result.count())
}
}
object funcs{
def func_1(i:Int): Int = {
i + 1
}
}
这样火花效果很好。
当我将其更改为以下方式时,它不起作用并抛出NotSerializableException
第二种方式:
package examples
import org.apache.spark.SparkConf
import org.apache.spark.SparkContext
object dd {
def main(args: Array[String]):Unit = {
val sparkConf = new SparkConf
val sc = new SparkContext(sparkConf)
val data = List(1,2,3,4,5)
val rdd = sc.makeRDD(data)
val handler = funcs
val result = rdd.map(elem => {
handler.func_1(elem)
})
println(result.count())
}
}
object funcs{
def func_1(i:Int): Int = {
i + 1
}
}
我知道我收到错误“task is not serializable”的原因是因为我试图在第二个例子中从驱动程序节点向worker节点发送一个不可序列化的对象funcs
。对于第二个示例,如果我使对象funcs
扩展Serializable
,则此错误将消失。
但是在我看来,因为funcs
是一个对象而不是一个类,它是一个单例,应该被序列化并从驱动程序发送到工作者而不是在工作节点本身内实例化。在这种情况下,尽管使用对象funcs
的方式不同,但我想在这两个示例中,无法序列化的对象funcs
从驱动程序节点发送到工作节点。
我的问题是为什么第一个例子可以成功运行,但第二个例子因'task unserializable'异常而失败。
答案 0 :(得分:6)
当您在RDD闭包中运行代码(映射,过滤等等)时,执行该代码所需的所有内容将被打包,序列化并发送到执行程序以进行运行。引用的任何对象(或其引用的字段)将在此任务中序列化,这是您有时会获得NotSerializableException
的地方。
但是,您的用例有点复杂,并且涉及scala编译器。通常,在scala对象上调用函数相当于调用java静态方法。该对象从未真正存在 - 它基本上就像编写内联代码一样。但是,如果将对象分配给变量,那么实际上是在内存中创建对该对象的引用,并且该对象的行为更像是类,并且可能存在序列化问题。
scala> object A {
def foo() {
println("bar baz")
}
}
defined module A
scala> A.foo() // static method
bar baz
scala> val a = A // now we're actually assigning a memory location
a: A.type = A$@7e0babb1
scala> a.foo() // dereferences a before calling foo
bar baz
答案 1 :(得分:4)
为了让Spark分发给定的操作,需要序列化操作中使用的函数。在序列化之前,这些函数会通过一个称为" ClosureCleaner"的复杂过程。
目的是"切断"从上下文中闭包,以减少需要序列化的对象图的大小,并降低过程中序列化问题的风险。换句话说,确保只有执行该函数所需的代码被序列化并发送以进行反序列化和执行#34;在另一侧"
在此过程中,闭包还被评估为Serializable,以便主动检测运行时的序列化问题(SparkContext#clean)。
该代码密集且复杂,因此很难找到导致这种情况的正确代码路径。
直观,当ClosureCleaner
发现时,会发生什么:
val result = rdd.map{elem =>
funcs.func_1(elem)
}
它将闭包的内部成员评估为来自can be recreated and there are no further references的对象,因此清理后的闭包只包含{elem => funcs.func_1(elem)}
,val handler = funcs
val result = rdd.map(elem => {
handler.func_1(elem)
})
可以对其进行序列化。
相反,当闭包清理器评估时:
$outer
它发现闭包有handler
({elem =>
val handler = funcs
handler.func_1(elem)
}
)的引用,因此它检查外部作用域并将和变量实例添加到已清理的闭包中。我们可以想象得到的清洁闭合是这种形状的东西(这仅用于说明目的):
handler
当对JavaSerializer
测试闭包时,它无法序列化。根据JVM序列化规则,如果递归地将其所有成员都可序列化,则该对象是可序列化的。在这种情况下,meta_key
引用一个不可序列化的对象,检查失败。