了解Spark序列化

时间:2016-11-26 11:46:03

标签: serialization apache-spark lambda spark-dataframe

在Spark中,如何知道哪些对象在驱动程序上实例化,哪些在executor上实例化,因此如何确定哪些类需要实现Serializable?

2 个答案:

答案 0 :(得分:36)

序列化对象意味着将其状态转换为字节流,以便可以将字节流还原为对象的副本。如果Java对象的类或其任何超类实现了java.io.Serializable接口或其子接口java.io.Externalizable,则该对象是可序列化的。

类永远不会序列化,只有类的对象被序列化。如果需要通过网络持久化或传输对象,则需要对象序列化。

Class Component            Serialization
instance variable           yes
Static instance variable    no
methods                     no
Static methods              no
Static inner class          no
local variables             no

让我们采用示例Spark代码并完成各种场景

public class SparkSample {

      public int instanceVariable                =10 ;
      public static int staticInstanceVariable   =20 ;

      public int run(){

         int localVariable                       =30;

         // create Spark conf
         final SparkConf sparkConf = new SparkConf().setAppName(config.get(JOB_NAME).set("spark.serializer", "org.apache.spark.serializer.KryoSerializer");

         // create spark context 
         final JavaSparkContext sparkContext = new JavaSparkContext(sparkConf);

        // read DATA 
        JavaRDD<String> lines = spark.read().textFile(args[0]).javaRDD(); 


        // Anonymous class used for lambda implementation
        JavaRDD<String> words = lines.flatMap(new FlatMapFunction<String, String>() {
                @Override
                public Iterator<String> call(String s) {
                // How will the listed varibles be accessed in RDD across driver and Executors 
                System.out.println("Output :" + instanceVariable + " " + staticInstanceVariable + " " + localVariable);
                return Arrays.asList(SPACE.split(s)).iterator();
        });

        // SAVE OUTPUT
        words.saveAsTextFile(OUTPUT_PATH));

      }

       // Inner Static class for the funactional interface which can replace the lambda implementation above 
       public static class MapClass extends FlatMapFunction<String, String>() {
                @Override
                public Iterator<String> call(String s) {
                System.out.println("Output :" + instanceVariable + " " + staticInstanceVariable + " " + localVariable);
                return Arrays.asList(SPACE.split(s)).iterator();
        }); 

        public static void main(String[] args) throws Exception {
            JavaWordCount count = new JavaWordCount();
            count.run();
        }
}

内部类对象内的外部类的实例变量的可访问性和可序列性

 Inner class           |   Instance Variable (Outer class)   | Static Instance Variable (Outer class)      |  Local Variable (Outer class)

     Anonymous class   |     Accessible And Serialized       | Accessible yet not Serialized                   |  Accessible And Serialized 

    Inner Static class | Not Accessible                      | Accessible yet not Serialized                   | Not Accessible 

了解Spark工作时的经验法则是:

  1. 在RDD中编写的所有lambda函数都在驱动程序上实例化,对象被序列化并发送给执行程序

  2. 如果在内部类中访问任何外部类实例变量,编译器会应用不同的逻辑来访问它们,因此外部类是否被序列化取决于您访问的内容。

  3. 就Java而言,整个争论是关于外类和内部类以及如何访问外部类引用和变量导致序列化问题。

  4. 各种情况:

    在Anonymous类中访问的外部变量变量:

    实例变量(外类)

    编译器默认在

    的字节代码中插入构造函数

    引用外类对象的匿名类。

    外部类对象用于访问实例变量

    匿名级(){

     final Outer-class reference;
    
     Anonymous-class( Outer-class outer-reference){
    
    reference = outer-reference;
    
    }
    

    }

    外部类被序列化并与之一起发送 内部匿名类的序列化对象

    静态实例变量(外部类)

    由于静态变量未被序列化,因此外部类 对象仍然插入到Anonymous类构造函数中。

    静态变量的值取自类状态

    出现在执行人身上。

    本地变量(外类)

    编译器默认在

    的字节代码中插入构造函数

    匿名类,引用外部类对象和局部变量引用。

    外部类对象用于访问实例变量

    匿名级(){

     final Outer-class reference;
    
    final Local-variable localRefrence ;
    
     Anonymous-class( Outer-class outer-reference, Local-variable localRefrence){
    
    reference = outer-reference;
    
    this.localRefrence = localRefrence;
    
    }
    

    }

    外部类是序列化的,局部变量对象也是

    序列化并与内部匿名类的序列化对象一起发送

    当局部变量成为匿名类中的实例成员时,需要对其进行序列化。从外部角度来看,局部变量永远不能被序列化

    ----------

    使用静态内部类访问的外部类变量。

    实例变量(外类)

    无法访问

    本地变量(外类)

    无法访问

    静态实例变量(外部类)

    由于静态变量未被序列化,因此没有外部类对象被序列化。

    静态变量的值取自类状态

    出现在执行人身上。

    外部类没有序列化,并与序列化的静态内部类一起发送

    要思考的要点:

    1. 遵循Java序列化规则来选择需要序列化的类对象。

    2. 使用javap -p -c&#34; abc.class&#34;打开字节代码并查看编译器生成的代码

    3. 根据您在外部类的内部类中尝试访问的内容,编译器会生成不同的字节代码。

    4. 您不需要让类实现序列化,只能在驱动程序上访问。

    5. 在RDD中使用的任何匿名/静态类(所有lambda函数都是匿名类)都将在驱动程序上实例化。

    6. RDD中使用的任何类/变量都将在驱动程序上实例化并发送给执行程序。

    7. 任何声明为transient的实例变量都不会在驱动程序上序列化。

      1. 默认情况下,匿名类将强制您使外部类可序列化。
      2. 任何局部变量/对象都不需要是可序列化的。
      3. 只有在Anonymous类中使用局部变量时才需要序列化
      4. 可以在pair,mapToPair函数的call()方法中创建单例,从而确保它永远不会在驱动程序上初始化
      5. 静态变量永远不会被序列化,因此永远不会发送 从司机到执行人
    8. 如果你需要只在执行者身上执行任何服务,在lambda函数中使它们成为静态字段,或者使它们成为瞬态和singelton并检查null条件以实例化它们

答案 1 :(得分:3)

有很多写得很好的博客,它们对此进行了很好的解释,例如spark serialization challenges

但是总之,我们可以得出这样的结论(仅火花,而不是一般的JVM):

  1. 由于JVM,只能将对象序列化(函数是对象)
  2. 如果对象需要序列化,则其父对象也需要序列化
  3. 任何Spark操作,例如(地图,flatMap,过滤器,foreachPartition,mapPartition等。),如果内部零件引用了外部零件对象,则该对象需要序列化。因为外部对象在驱动程序中,而不在执行程序中。序列化策略指的是我的第二点。
  4. 对Scala object(又名Scala单例)的引用将不会被序列化(仅对于mapPartition和foreachPartition,UDF始终从驱动程序到执行程序都被序列化)。执行者将直接引用其本地JVM的对象,因为它是一个单例,将存在于执行者JVM上。这意味着,执行者不会看到其本地object上的Driver突变。