Задача не сериализуема: java.io.NotSerializableException при вызове функции вне замыкания только для классов, а не объектов

171

Получение странного поведения при вызове функции за пределами замыкания:

  • когда функция находится в объекте, все работает
  • когда функция находится в классе get:

Задача не сериализуема: java.io.NotSerializableException: тестирование

Проблема в том, что мне нужен мой код в классе, а не в объекте. Любая идея, почему это происходит? Является ли объект Scala сериализованным (по умолчанию?)?

Это пример рабочего кода:

object working extends App {
    val list = List(1,2,3)

    val rddList = Spark.ctx.parallelize(list)
    //calling function outside closure 
    val after = rddList.map(someFunc(_))

    def someFunc(a:Int)  = a+1

    after.collect().map(println(_))
}

Это нерабочий пример:

object NOTworking extends App {
  new testing().doIT
}

//adding extends Serializable wont help
class testing {  
  val list = List(1,2,3)  
  val rddList = Spark.ctx.parallelize(list)

  def doIT =  {
    //again calling the fucntion someFunc 
    val after = rddList.map(someFunc(_))
    //this will crash (spark lazy)
    after.collect().map(println(_))
  }

  def someFunc(a:Int) = a+1
}
  • 0
    Что такое Spark.ctx? Нет объекта Spark с методом ctx AFAICT
Теги:
apache-spark
serialization
typesafe

6 ответов

240
Лучший ответ

Я не думаю, что другой ответ полностью правильный. RDD действительно сериализуемы, так что это не то, что приводит к сбою вашей задачи.

Spark - это распределенный вычислительный движок, а его основная абстракция - это гибкий распределенный набор данных (RDD), который можно рассматривать как распределенную коллекцию. В принципе, элементы RDD разбиваются по узлам кластера, но Spark абстрагирует это от пользователя, позволяя пользователю взаимодействовать с RDD (коллекцией), как если бы он был локальным.

Не вдаваться в слишком много деталей, но когда вы запускаете различные преобразования на RDD (map, flatMap, filter и другие), ваш код преобразования (закрытия):

  • сериализован в драйвере node,
  • отправляется в соответствующие узлы кластера,
  • десериализации,
  • и, наконец, выполняется на узлах

Вы можете, конечно, запустить это локально (как в вашем примере), но все эти фазы (помимо доставки по сети) все еще происходят. [Это позволяет вам улавливать любые ошибки даже до развертывания на производстве]

Что происходит во втором случае, так это то, что вы вызываете метод, определенный в классе testing изнутри функции карты. Spark видит это, и поскольку методы не могут быть сериализованы самостоятельно, Spark пытается сериализовать весь класс testing, так что код будет работать, когда выполняется в другой JVM. У вас есть две возможности:

Либо вы выполняете сериализацию классов, поэтому весь класс может быть сериализован Spark:

import org.apache.spark.{SparkContext,SparkConf}

object Spark {
  val ctx = new SparkContext(new SparkConf().setAppName("test").setMaster("local[*]"))
}

object NOTworking extends App {
  new Test().doIT
}

class Test extends java.io.Serializable {
  val rddList = Spark.ctx.parallelize(List(1,2,3))

  def doIT() =  {
    val after = rddList.map(someFunc)
    after.collect().foreach(println)
  }

  def someFunc(a: Int) = a + 1
}

или вы вместо функции someFunc выполняете функцию (функции - объекты в Scala), поэтому Spark сможет сериализовать ее:

import org.apache.spark.{SparkContext,SparkConf}

object Spark {
  val ctx = new SparkContext(new SparkConf().setAppName("test").setMaster("local[*]"))
}

object NOTworking extends App {
  new Test().doIT
}

class Test {
  val rddList = Spark.ctx.parallelize(List(1,2,3))

  def doIT() =  {
    val after = rddList.map(someFunc)
    after.collect().foreach(println)
  }

  val someFunc = (a: Int) => a + 1
}

Аналогичная, но не та же проблема с сериализацией классов может вас заинтересовать, и вы можете прочитать на ней в этой презентации Spark Summit 2013.

Как примечание, вы можете переписать rddList.map(someFunc(_)) на rddList.map(someFunc), они точно такие же. Обычно второй предпочтительнее, поскольку он менее подробный и более чистый для чтения.

EDIT (2015-03-15): SPARK-5307 представил SerializationDebugger, а Spark 1.3.0 - первая версия для использования, Он добавляет путь сериализации к NotSerializableException. Когда встречается исключение NotSerializableException, отладчик посещает граф объектов, чтобы найти путь к объекту, который не может быть сериализован, и создает информацию, чтобы помочь пользователю найти объект.

В случае OP это то, что печатается в stdout:

Serialization stack:
    - object not serializable (class: testing, value: testing@2dfe2f00)
    - field (class: testing$$anonfun$1, name: $outer, type: class testing)
    - object (class testing$$anonfun$1, <function1>)
  • 1
    Хм, то, что вы объяснили, безусловно, имеет смысл, и объясняет, почему весь класс get сериализован (что я не до конца понял). Тем не менее, я по-прежнему буду считать, что rdd не сериализуемы (хорошо, они расширяют Serializable, но это не значит, что они не вызывают NotSerializableException, попробуйте). Вот почему, если вы разместите их вне классов, это исправит ошибку. Я собираюсь немного отредактировать свой ответ, чтобы быть более точным в том, что я имею в виду - то есть они вызывают исключение, а не расширяют интерфейс.
  • 25
    Если у вас нет контроля над классом, вам нужно быть сериализуемым ... если вы используете Scala, вы можете просто создать его экземпляр с помощью Serializable: val test = new Test with Serializable
Показать ещё 1 комментарий
29

Ответ Grega отлично объясняет, почему исходный код не работает, и два способа исправить проблему. Однако это решение не очень гибко; рассмотрите случай, когда ваше закрытие включает вызов метода в классе Serializable, который у вас нет. Вы не можете добавить тег Serializable в этот класс или изменить базовую реализацию, чтобы изменить метод на функцию.

Nilesh представляет большой обходной путь для этого, но решение может быть сделано как более сжатым, так и общим:

def genMapper[A, B](f: A => B): A => B = {
  val locker = com.twitter.chill.MeatLocker(f)
  x => locker.get.apply(x)
}

Этот функционал-сериализатор затем может использоваться для автоматического переноса замыканий и вызовов методов:

rdd map genMapper(someFunc)

Этот метод также имеет преимущество, не требуя дополнительных зависимостей Shark, чтобы получить доступ к KryoSerializationWrapper, поскольку Twitter Chill уже вытащен ядром Spark

  • 0
    Привет, интересно, нужно ли мне что-то регистрировать, если я использую твой код? Я попытался получить исключение класса Unable find от kryo. Спасибо
21

Полный разговор, полностью объясняющий проблему, которая предлагает отличную парадигму, позволяющую избежать этих проблем сериализации: https://github.com/samthebest/dump/blob/master/sams-scala-tutorial/serialization-exceptions-and-memory- leaks-no-ws.md

Высший проголосовавший ответ в основном предлагает отбросить всю языковую функцию - это больше не использует методы и использует только функции. В самом деле, в методах функционального программирования в классах следует избегать, но превращение их в функции не решает проблему дизайна здесь (см. Ссылку выше).

В качестве быстрого исправления в этой конкретной ситуации вы можете просто использовать аннотацию @transient чтобы сказать ей, чтобы она не пыталась сериализовать оскорбительное значение (здесь Spark.ctx - это настраиваемый класс, а не Spark, следующий после имен OP):

@transient
val rddList = Spark.ctx.parallelize(list)

Вы также можете реструктурировать код, чтобы rddList проживал где-то в другом месте, но это также противно.

Будущее, вероятно, спор

В будущем Scala будет включать в себя такие вещи, которые называются "спорами", которые должны позволить нам контролировать мелкие зерна, что делает и не совсем затягивается закрытием. Кроме того, это должно привести к ошибкам случайного вытягивания несериализуемых типов (или любых нежелательных значений) в ошибки компиляции, а не в настоящее время, что является ужасным исключением/ошибками в работе.

http://docs.scala-lang.org/sips/pending/spores.html

Совет по сериализации Kryo

При использовании kyro сделайте так, чтобы регистрация была необходима, это будет означать, что вы получите ошибки вместо утечек памяти:

"Наконец, я знаю, что у kryo есть kryo.setRegistrationOptional (правда), но у меня очень трудное время, пытаясь выяснить, как его использовать. Когда этот параметр включен, kryo все еще, кажется, бросает исключения, если я не зарегистрирован классы ".

Стратегия регистрации классов с помощью крио

Конечно, это только дает вам контроль уровня на уровне, а не контроль уровня ценности.

... больше идей.

6

Я не совсем уверен, что это относится к Scala, но в Java я решил NotSerializableException путем рефакторинга моего кода, чтобы закрытие не получило доступ к несериализуемому полю final.

  • 0
    Я сталкиваюсь с той же проблемой в Java, я пытаюсь использовать класс FileWriter из пакета Java IO внутри метода fored RDD. Пожалуйста, дайте мне знать, как мы можем решить это.
  • 1
    Хорошо @Shankar, если FileWriter является final полем внешнего класса, вы не можете сделать это. Но FileWriter может быть построен из String или File , оба из которых являются Serializable . Поэтому рефакторинг вашего кода для создания локального FileWriter на основе имени файла из внешнего класса.
6

Я решил эту проблему, используя другой подход. Вам просто нужно сериализовать объекты перед тем, как пройти через закрытие, и затем де-сериализовать. Этот подход просто работает, даже если ваши классы не являются Serializable, потому что он использует Kryo за кулисами. Все, что вам нужно, это карри.;)

Вот пример того, как я это сделал:

def genMapper(kryoWrapper: KryoSerializationWrapper[(Foo => Bar)])
               (foo: Foo) : Bar = {
    kryoWrapper.value.apply(foo)
}
val mapper = genMapper(KryoSerializationWrapper(new Blah(abc))) _
rdd.flatMap(mapper).collectAsMap()

object Blah(abc: ABC) extends (Foo => Bar) {
    def apply(foo: Foo) : Bar = { //This is the real function }
}

Не стесняйтесь сделать Blah настолько сложным, насколько вы хотите, класс, сопутствующий объект, вложенные классы, ссылки на несколько сторонних библиотек.

KryoSerializationWrapper относится к: https://github.com/amplab/shark/blob/master/src/main/scala/shark/execution/serialization/KryoSerializationWrapper.scala

  • 0
    Означает ли это сериализацию экземпляра или создание статического экземпляра и сериализацию ссылки (см. Мой ответ).
  • 2
    @samthebest не могли бы вы уточнить? Если вы исследуете KryoSerializationWrapper вы обнаружите, что он заставляет Spark думать, что это действительно java.io.Serializable - он просто сериализует объект внутри, используя Kryo - быстрее, проще. И я не думаю, что он имеет дело со статическим экземпляром - он просто десериализует значение при вызове value.apply ().
4

Я столкнулся с подобной проблемой, и то, что я понимаю из ответа Греги,

object NOTworking extends App {
 new testing().doIT
}
//adding extends Serializable wont help
class testing {

val list = List(1,2,3)

val rddList = Spark.ctx.parallelize(list)

def doIT =  {
  //again calling the fucntion someFunc 
  val after = rddList.map(someFunc(_))
  //this will crash (spark lazy)
  after.collect().map(println(_))
}

def someFunc(a:Int) = a+1

}

ваш метод doIT пытается сериализовать метод someFunc (_), но поскольку метод не является сериализуемым, он пытается сериализовать класс тестирование который снова не сериализуем.

Итак, создайте свой код, вы должны определить someFunc внутри метода doIT. Например:

def doIT =  {
 def someFunc(a:Int) = a+1
  //function definition
 }
 val after = rddList.map(someFunc(_))
 after.collect().map(println(_))
}

И если в картинке присутствует несколько функций, все эти функции должны быть доступны для родительского контекста.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню