Понимание сериализации Spark

В Spark, как узнать, какие объекты создаются на драйвере и которые создаются на исполнителе, и, следовательно, как определить, какие классы необходимо реализовать Serializable?

Ответ 1

Сериализация объекта означает преобразование его состояния в поток байтов, чтобы поток байтов мог быть возвращен обратно в копию объекта. Объект 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:

  • Все функции лямбда, записанные внутри RDD, создаются в драйвере, а объекты сериализуются и отправляются исполнителям

  • Если во внутреннем классе обращаются какие-либо внешние переменные экземпляра класса, компилятор применяет различную логику для доступа к ним, поэтому внешний класс становится сериализованным или не зависит от того, к чему вы обращаетесь.

  • В терминах Java все рассуждения касаются класса Outer класса vs Inner и того, как доступ к ссылкам и переменным внешнего класса приводит к проблемам с сериализацией.

Различные сценарии:

Внешний класс Переменные переменные, доступные в классе Anonymous:


Переменная экземпляра (внешний класс)

Компилятор по умолчанию вставляет конструктор в байтовый код

Анонимный класс со ссылкой на объект класса Outer.

Внешний объект класса используется для доступа к переменной экземпляра

Анонимные класса() {

 final Outer-class reference;

 Anonymous-class( Outer-class outer-reference){

reference = outer-reference;

}

}

Внешний класс сериализуется и отправляется вместе с сериализованный объект внутреннего анонимного класса


Статическая переменная экземпляра (внешний класс)

Поскольку статические переменные не являются сериализованными, внешний класс объект все еще вставлен в конструктор анонимного класса.

Значение статической переменной берется из состояния класса

присутствует на этом исполнителе.


Локальная переменная (внешний класс)

Компилятор по умолчанию вставляет конструктор в байтовый код

Анонимный класс со ссылкой на объект Outer class и локальное переменное refrence.

Внешний объект класса используется для доступа к переменной экземпляра

Анонимные класса() {

 final Outer-class reference;

final Local-variable localRefrence ;

 Anonymous-class( Outer-class outer-reference, Local-variable localRefrence){

reference = outer-reference;

this.localRefrence = localRefrence;

}

}

Внешний класс сериализуется, а локальный объект переменной также

сериализован и отправлен вместе с сериализованным объектом внутреннего анонимного класса

Поскольку локальная переменная становится членом экземпляра внутри анонимного класса, она должна быть сериализована. С точки зрения внешнего класса локальная переменная никогда не может быть сериализована

----------

Внешние переменные класса, доступ к которым осуществляется со статическим внутренним классом.

Переменная экземпляра (внешний класс)

невозможно получить доступ


Локальная переменная (внешний класс)

невозможно получить доступ


Статическая переменная экземпляра (внешний класс)

Поскольку статические переменные не сериализуются, следовательно, объект внешнего класса не сериализуется.

Значение статической переменной берется из состояния класса

присутствует на этом исполнителе.

Внешний класс не сериализуется и отправляется вместе с сериализованным внутренним классом Static


Очки для размышлений:

  • Применяются правила Java Serialization для выбора того, какой объект класса должен быть сериализован.

  • Используйте javap -p -c "abc.class", чтобы развернуть байтовый код и посмотреть код сгенерированного компилятором

  • В зависимости от того, что вы пытаетесь получить во внутреннем классе внешнего класса, компилятор генерирует разные байтовые коды.

  • Вам не нужно делать классы, реализующие сериализацию, доступ к которой возможен только на драйвере.

  • Любой анонимный/статический класс (все лямбда-функции - это анонимный класс), используемый в RDD, будет создан на драйвере.

  • Любой класс/переменная, используемая внутри RDD, будет инстанцироваться на драйвер и отправляться исполнителям.

  • Любая переменная, объявленная переменным экземпляра, не будет сериализована в драйвере.

    1. По умолчанию анонимные классы заставят вас сделать внешний класс сериализуемым.
    2. Любая локальная переменная/объект не обязательно должна быть сериализована.
    3. Только если локальная переменная используется внутри класса Anonymous, должна быть сериализована
    4. Можно создать singleton внутри метода call() для пары, функции mapToPair, таким образом, чтобы он никогда не инициализировался на драйвере
    5. статические переменные никогда не сериализуются, поэтому никогда не отправляются от водителя к исполнителям
если вам нужна любая служба, которая должна выполняться только на исполнителе, сделать их статическими полями внутри лямбда-функции или сделать их переходными и singelton и проверить наличие нулевого условия для их создания

Ответ 2

есть много очень хорошо написанных блогов, которые очень хорошо объясняют это, например, этот: вызов сериализации.

но вкратце, мы можем сделать вывод следующим образом (только Spark, а не JVM в целом):

  1. из-за JVM сериализуются только объекты (функции являются объектами)
  2. если объект должен быть сериализован, его родительский объект также должен быть сериализован
  3. любые операции Spark, такие как (map, flatMap, filter, foreachPartition, mapPartition и т.д.), если внутренняя часть имеет ссылку на объект внешней части, этот объект необходимо сериализовать. Потому что объекты внешней части находятся в Driver, а не в Executors. И политика сериализации относится к моей точке № 2.
  4. ссылка на Scala object (она же синглтон Scala) не будет сериализована (только для mapPartition и foreachPartition, UDF всегда будет получать serde от драйвера до исполнителя). исполнители будут напрямую ссылаться на свой локальный объект JVM, поскольку он будет существовать в JVM исполнителей. Это означает, что мутация драйвера на его локальном object не будет видна от исполнителей.