[英]Use Serializable lambda in Spark JavaRDD transformation
我正在嘗試理解以下代碼。
// 文件:LambdaTest.java
package test;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
public class LambdaTest implements Ops {
public static void main(String[] args) {
new LambdaTest().job();
}
public void job() {
SparkConf conf = new SparkConf()
.setAppName(LambdaTest.class.getName())
.setMaster("local[*]");
JavaSparkContext jsc = new JavaSparkContext(conf);
List<Integer> lst = Arrays.asList(1, 2, 3, 4, 5, 6);
JavaRDD<Integer> rdd = jsc.parallelize(lst);
Function<Integer, Integer> func1 = (Function<Integer, Integer> & Serializable) x -> x * x;
Function<Integer, Integer> func2 = x -> x * x;
System.out.println(func1.getClass()); //test.LambdaTest$$Lambda$8/390374517
System.out.println(func2.getClass()); //test.LambdaTest$$Lambda$9/208350681
this.doSomething(rdd, func1); // works
this.doSomething(rdd, func2); // org.apache.spark.SparkException: Task not serializable
}
}
// 文件:Ops.java
package test;
import org.apache.spark.api.java.JavaRDD;
import java.util.function.Function;
public interface Ops {
default void doSomething(JavaRDD<Integer> rdd, Function<Integer, Integer> func) {
rdd.map(x -> x + func.apply(x))
.collect()
.forEach(System.out::println);
}
}
不同之處在於func1
是使用Serializable
綁定的,而func2
不是。
查看這兩個函數的運行時類時,它們都是LambdaTest
類下的匿名類
它們都用於接口中的 RDD 轉換,那么這兩個函數和LambdaTest
應該是可序列化的。
如您所見, LambdaTest
沒有實現Serializable
接口。 所以我認為這兩個 func 不應該工作。 但令人驚訝的是, func1
有效。
func2
的堆棧跟蹤如下:
Serialization stack:
- object not serializable (class: test.LambdaTest$$Lambda$9/208350681, value: test.LambdaTest$$Lambda$9/208350681@61d84e08)
- element of array (index: 0)
- array (class [Ljava.lang.Object;, size 1)
- field (class: java.lang.invoke.SerializedLambda, name: capturedArgs, type: class [Ljava.lang.Object;)
- object (class java.lang.invoke.SerializedLambda, SerializedLambda[capturingClass=interface fr.leboncoin.etl.jobs.test.Ops, functionalInterfaceMethod=org/apache/spark/api/java/function/Function.call:(Ljava/lang/Object;)Ljava/lang/Object;, implementation=invokeStatic fr/leboncoin/etl/jobs/test/Ops.lambda$doSomething$1024e30a$1:(Ljava/util/function/Function;Ljava/lang/Integer;)Ljava/lang/Integer;, instantiatedMethodType=(Ljava/lang/Integer;)Ljava/lang/Integer;, numCaptured=1])
- writeReplace data (class: java.lang.invoke.SerializedLambda)
- object (class fr.leboncoin.etl.jobs.test.Ops$$Lambda$10/1470295349, fr.leboncoin.etl.jobs.test.Ops$$Lambda$10/1470295349@4e1459ea)
- field (class: org.apache.spark.api.java.JavaPairRDD$$anonfun$toScalaFunction$1, name: fun$1, type: interface org.apache.spark.api.java.function.Function)
- object (class org.apache.spark.api.java.JavaPairRDD$$anonfun$toScalaFunction$1, <function1>)
at org.apache.spark.serializer.SerializationDebugger$.improveException(SerializationDebugger.scala:40)
at org.apache.spark.serializer.JavaSerializationStream.writeObject(JavaSerializer.scala:47)
at org.apache.spark.serializer.JavaSerializerInstance.serialize(JavaSerializer.scala:81)
at org.apache.spark.util.ClosureCleaner$.ensureSerializable(ClosureCleaner.scala:312)
... 19 more
似乎如果一個函數與Serializable
綁定,則包含它的對象不需要序列化,這讓我感到困惑。
對此的任何解釋都將受到高度贊賞。
- - - - - - - - - - - - - - - 更新 - - - - - - - - - - ------------
我嘗試使用抽象類而不是接口:
//文件:AbstractTest.java
public class AbstractTest {
public static void main(String[] args) {
new AbstractTest().job();
}
public void job() {
SparkConf conf = new SparkConf()
.setAppName(AbstractTest.class.getName())
.setMaster("local[*]");
JavaSparkContext jsc = new JavaSparkContext(conf);
List<Integer> lst = Arrays.asList(1, 2, 3, 4, 5, 6);
JavaRDD<Integer> rdd = jsc.parallelize(lst);
Ops ops = new Ops() {
@Override
public Integer apply(Integer x) {
return x + 1;
}
};
System.out.println(ops.getClass()); // class fr.leboncoin.etl.jobs.test.AbstractTest$1
ops.doSomething(rdd);
}
}
// 文件:Ops.java
public abstract class Ops implements Serializable{
public abstract Integer apply(Integer x);
public void doSomething(JavaRDD<Integer> rdd) {
rdd.map(x -> x + apply(x))
.collect()
.forEach(System.out::println);
}
}
即使Ops
類使用AbstractTest
類編譯在單獨的文件中,它也不起作用。 ops
對象的類名是class fr.leboncoin.etl.jobs.test.AbstractTest$1
。 根據以下堆棧跟蹤,似乎需要序列化AbstractTest
才能序列化AbstractTest$1
。
Serialization stack:
- object not serializable (class: test.AbstractTest, value: test.AbstractTest@21ac5eb4)
- field (class: test.AbstractTest$1, name: this$0, type: class test.AbstractTest)
- object (class test.AbstractTest$1, test.AbstractTest$1@36fc05ff)
- element of array (index: 0)
- array (class [Ljava.lang.Object;, size 1)
- field (class: java.lang.invoke.SerializedLambda, name: capturedArgs, type: class [Ljava.lang.Object;)
- object (class java.lang.invoke.SerializedLambda, SerializedLambda[capturingClass=class fr.leboncoin.etl.jobs.test.Ops, functionalInterfaceMethod=org/apache/spark/api/java/function/Function.call:(Ljava/lang/Object;)Ljava/lang/Object;, implementation=invokeSpecial fr/leboncoin/etl/jobs/test/Ops.lambda$doSomething$6d6228b6$1:(Ljava/lang/Integer;)Ljava/lang/Integer;, instantiatedMethodType=(Ljava/lang/Integer;)Ljava/lang/Integer;, numCaptured=1])
- writeReplace data (class: java.lang.invoke.SerializedLambda)
- object (class fr.leboncoin.etl.jobs.test.Ops$$Lambda$8/208350681, fr.leboncoin.etl.jobs.test.Ops$$Lambda$8/208350681@4acb2510)
- field (class: org.apache.spark.api.java.JavaPairRDD$$anonfun$toScalaFunction$1, name: fun$1, type: interface org.apache.spark.api.java.function.Function)
- object (class org.apache.spark.api.java.JavaPairRDD$$anonfun$toScalaFunction$1, <function1>)
at org.apache.spark.serializer.SerializationDebugger$.improveException(SerializationDebugger.scala:40)
at org.apache.spark.serializer.JavaSerializationStream.writeObject(JavaSerializer.scala:47)
at org.apache.spark.serializer.JavaSerializerInstance.serialize(JavaSerializer.scala:81)
at org.apache.spark.util.ClosureCleaner$.ensureSerializable(ClosureCleaner.scala:312)
... 19 more
LambdaTest
不需要是可Serializable
的,因為它不是通過網絡發送的——沒有理由這樣做。
另一方面, func1
和func1
都必須是可Serializable
的,因為 Spark 將使用它們來執行計算(在RDD上,因此此代碼必須通過線路發送到工作節點。請注意,即使您編寫它都在同一個類中,編譯后你的 lambdas 將被放在單獨的文件中,這要歸功於整個類不必通過網絡發送 -> 外部類不需要是Serializable
。
至於為什么fun1
有效,當您不使用類型轉換時,Java 編譯器會為您推斷 lambda 表達式的類型。 所以在這種情況下,為fun2
生成的代碼將簡單地實現一個Function
(因為這是目標變量的類型)。 另一方面,如果無法從上下文中推斷出類型(例如在您的情況下,編譯器無法知道fun1
必須是可Serializable
的,因為它是 Spark 所需的功能),您可以像示例中那樣使用類型轉換來顯式提供一個類型。 在這種情況下,編譯器生成的代碼將同時實現Function
和Serializable
接口,編譯器不會嘗試自行推斷類型。
你可以在5. Contexts for target typing下的 lambda 狀態中找到它的描述。
上面的答案是正確的。 至於附加抽象類的問題,答案是AbstractTest類中實現的抽象類是一個內部類,有outclass的引用。 當序列化一個對象時,它會序列化它的字段,一個外類 AbstractTest 是不可序列化的,因此它不能被序列化。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.