假设我有以下代码:
class Context {
def compute() = Array(1.0)
}
val ctx = new Context
val data = ctx.compute
现在我们在Spark中运行此代码:
val rdd = sc.parallelize(List(1,2,3))
rdd.map(_ + data(0)).count()
上面的代码抛出org.apache.spark.SparkException: Task not serializable
。我不问如何解决它,通过扩展Serializable或创建一个case类,我想了解错误发生的原因。
我不理解的是为什么它抱怨Context类不是Serializable
,尽管它不是lambda的一部分:rdd.map(_ + data(0))
。 data
这里是一个应该序列化的值数组,但似乎JVM也捕获了ctx
引用,在我的理解中,它不应该发生。
据我了解,在shell中,Spark应该从repl上下文中清除lambda。如果我们在delambdafy
阶段之后打印树,我们会看到这些部分:
object iw extends Object {
...
private[this] val ctx: $line11.iw$Context = _;
<stable> <accessor> def ctx(): $line11.iw$Context = iw.this.ctx;
private[this] val data: Array[Double] = _;
<stable> <accessor> def data(): Array[Double] = iw.this.data;
...
}
class anonfun$1 ... {
final def apply(x$1: Int): Double = anonfun$1.this.apply$mcDI$sp(x$1);
<specialized> def apply$mcDI$sp(x$1: Int): Double = x$1.+(iw.this.data().apply(0));
...
}
因此,发送到工作节点的反编译lambda代码为:x$1.+(iw.this.data().apply(0))
。部分iw.this
属于Spark-Shell会话,因此,据我所知,它应该被ClosureCleaner清除,因为它与逻辑无关,不应该被序列化。无论如何,调用iw.this.data()
会返回Array[Double]
变量的data
值,该变量在构造函数中初始化:
def <init>(): type = {
iw.super.<init>();
iw.this.ctx = new $line11.iw$Context();
iw.this.data = iw.this.ctx().compute(); // <== here
iw.this.res4 = ...
()
}
在我的理解中ctx
值与lambda无关,它不是闭包,因此不应该序列化。我错过了什么或误解了什么?
答案 0 :(得分:1)
这与Spark认为可以安全地用作闭包的内容有关。这在某些情况下非常直观,因为Spark使用反射并且在许多情况下无法识别Scala的某些保证(不是完整的编译器或任何东西)或者同一对象中的某些变量无关紧要的事实。为了安全起见,Spark将尝试序列化引用的任何对象,在您的情况下包括iw
,这是不可序列化的。
ClosureCleaner中的代码有一个很好的例子:
例如,以下需要传递性清洁 情形:
class SomethingNotSerializable { def someValue = 1 def scope(name: String)(body: => Unit) = body def someMethod(): Unit = scope("one") { def x = someValue def y = 2 scope("two") { println(y + 1) } } }
在这个例子中,范围&#34;两个&#34;是不可序列化的,因为它引用了范围&#34;一个&#34;,它引用了SomethingNotSerializable。但请注意,范围的主体&#34; 2&#34;实际上并不依赖于SomethingNotSerializable。这意味着我们可以安全地清空克隆范围的父指针&#34;一个&#34;并将其设置为范围的父级&#34; 2&#34;,这样范围&#34; 2&#34;不再传递SomethingNotSerializable。
最简单的修复方法可能是在同一范围内创建一个局部变量,从对象中提取值,这样就不再对lambda中的封装对象进行任何引用了:
val rdd = sc.parallelize(List(1,2,3))
val data0 = data(0)
rdd.map(_ + data0).count()