我知道有很多关于此的问题,但我无法找到确切需要序列化(以及何时序列化)的系统解释......以及如何验证此要求。
考虑一下:
class Baz
trait Bar { val baz = new Baz; def bar(i: Int) = baz }
case object Foo extends Bar { def foo = sc.parallelize(1 to 1).map(bar).collect }
Foo.foo
这样可行,并返回Array(null)
对任何人都有意义吗???
如果我将val
更改为lazy val
,则停止工作,并抛出NotSerializableException
,这是有意义的 - 它会初始化baz
远程端,然后无法将其发回。
但是为什么在第一种情况下用null
替换它呢
如果我写它,几乎,我能想到的任何其他方式 - 例如将bar
定义从trait移动到object,或者用bar
替换_ => baz
调用 - 它也是停止工作,并抱怨Task is not serializable
。
返回在特征中定义的val的方法是什么,这使得它只是将其写为null
?有什么想法吗?
更新
上面的行为发生在带有spark 2.1.1的scala 2.11上。
Scala 2.10(spark 1.6.0)确实抛出异常,抱怨Baz
不可序列化......所以,这似乎是一个回归。
另外我注意到在spark 1.6.0上,这样的东西运行正常:
object Foo { def foo = sc.parallelize(1 to 1).map(bar).collect; def bar(i: Int) = i+1 }
Foo.foo
但是在spark 2.1.1上它抱怨Foo
不可序列化。这是为什么?
显然,序列化lambda还希望序列化Foo
,其中排序有意义......除了 以某种方式在1.6.0中工作,即使我让labda实际引用Foo
中的其他内容:
object Foo {
var stuff = 10
def foo = sc.parallelize(1 to 1).map(bar).collect
def bar(i: Int) = { stuff += 1; i+1 }
}
Foo.foo
Foo.stuff
这在1.6.0中工作正常,但在2.1.1中没有。
所以,这里有一个问题是它如何在1.6.0中实际运行?我的意思是,Foo
不可序列化,它如何知道另一端stuff
的价值?
另一个显而易见的问题是 - 为什么它在2.1.1中停止工作? 1.6.0行为是否存在细微问题,我们是否应该依赖它? 或者它只是2.1.1中的错误?
答案 0 :(得分:0)
从某种意义上说,这可能不是一个直接的答案,它可以为您提供Spark中序列化问题背后的确切推理,以及为什么您的用例可能会以这种或那种方式工作,但是......让我说清楚一下此
SparkContext
就是这一切发生的地方(至少在它开始的地方)。在这些方法中,您可以找到clean方法:
private[spark] def clean[F <: AnyRef](f: F, checkSerializable: Boolean = true): F = {
ClosureCleaner.clean(f, checkSerializable)
f
}
引用其scaladoc,您应该获得有关Spark如何进行序列化验证的足够信息:
清理一个闭包,使其准备好序列化并发送给任务(删除$ outer中的未引用变量,更新REPL变量)
如果设置了
checkSerializable
,clean
还会主动检查f
是否可序列化,如果没有,则抛出SparkException
。
搜索使用该方法的所有位置可能会对您的代码可能起作用或不起作用的原因有所了解。这就像将clean
方法应用于您的代码一样简单。
您也可以从找到clean
方法的RDD.map运算符开始:
val cleanF = sc.clean(f)
通过这种方式,您可以了解为什么您的代码可以根据您使用val
还是lazy val
提供不同的结果。
我认为最终您的代码可以重写如下:
// run spark-shell -c spark.driver.allowMultipleContexts=true
// use :paste -raw
package org.apache.spark
class Baz
trait Bar { lazy val baz = new Baz; def bar(i: Int) = baz }
case object Foo extends Bar {
val sc = new SparkContext("local[*]", "Clean", new SparkConf)
def foo = sc.clean(bar _)
}
// org.apache.spark.Foo.foo
我使用了spark-shell
,看起来代码在今天构建的Spark 2.3.0-SNAPSHOT 中使用和不使用lazy
关键字时运行良好。