【问题标题】:Use Serializable lambda in Spark JavaRDD transformation在 Spark JavaRDD 转换中使用 Serializable lambda
【发布时间】:2015-10-26 18:08:12
【问题描述】:

我正在尝试理解以下代码。

// 文件: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

【问题讨论】:

    标签: java lambda apache-spark serializable


    【解决方案1】:

    LambdaTest 不需要是Serializable,因为它不是通过网络发送的——没有理由这样做。

    另一方面,func1func1 都必须是 Serializable,因为 Spark 将使用它们来执行计算(在 RDD 上,因此这段代码必须是通过线路发送到工作节点。请注意,即使您将它们全部编写在同一个类中,编译后您的 lambdas 也会放在单独的文件中,这要归功于整个类不必通过线路发送 - > 外部类不需要是Serializable

    至于fun1 工作的原因,当您不使用类型转换时,Java 编译器会为您推断 lambda 表达式的类型。所以在这种情况下,为fun2 生成的代码将简单地实现Function(因为这是目标变量的类型)。另一方面,如果无法从上下文中推断出类型(就像在您的情况下,编译器无法知道 fun1 必须是 Serializable,因为它是 Spark 所需的功能),您可以使用类型转换作为在您的示例中明确提供类型。在这种情况下,编译器生成的代码将同时实现FunctionSerializable 接口,编译器不会尝试自行推断类型。

    您可以在 5 下的the state of lambda 中找到它的描述。目标类型的上下文

    【讨论】:

    • 我不确定你说的是什么,“请注意,即使你在同一个类中编写它们,编译后你的 lambdas 也会放在单独的文件中,这要感谢整个类没有'不必通过网络发送”。我已经用抽象类而不是接口更新了我的帖子,知道抽象类也被编译在一个单独的文件中。
    • @HaoRen 好吧,我确实告诉过你LambdaTest 不必是可序列化的,因为它没有被序列化并通过网络发送。为什么你认为它必须是?只有在 RDD 转换中使用的对象必须是可序列化的,因为 Spark 正在对它们执行操作。这就是为什么func1func2 必须实现Serializable
    • LambdaTest 没问题,我完全理解你所说的 LambdaTest。请查看我在上一条评论中谈到的原始帖子的更新。我不是在谈论 LambdaTest ......实际上,我使用抽象类而不是接口来做同样的事情,它也被编译在一个单独的文件中,因为它是一个匿名类。这是行不通的。这让我认为你在单独文件上的观点是不正确的。您还可以查看我在 spark 论坛上提出的相关问题:mail-archives.apache.org/mod_mbox/spark-user/201508.mbox/…。如果需要,请随时询问更多详细信息。
    猜你喜欢
    • 2017-05-09
    • 1970-01-01
    • 2016-07-24
    • 1970-01-01
    • 2020-06-01
    • 2021-04-12
    • 1970-01-01
    • 1970-01-01
    • 2017-02-25
    相关资源
    最近更新 更多