Spark序列化(?)具有特征怪异和1.6.0 ang 2.1.1之间的差异

时间:2017-06-21 00:05:28

标签: scala apache-spark serialization

我知道有很多关于此的问题,但我无法找到确切需要序列化(以及何时序列化)的系统解释......以及如何验证此要求。

考虑一下:

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中的错误?

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变量)

     

如果设置了checkSerializableclean还会主动检查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关键字时运行良好。