it-swarm.com.de

Task nicht serialisierbar: Java.io.NotSerializableException beim Aufrufen der Funktion außerhalb des Closure nur für Klassen, nicht für Objekte

Seltsames Verhalten beim Aufrufen einer Funktion außerhalb eines Closures:

  • wenn sich die Funktion in einem Objekt befindet, funktioniert alles
  • wenn function in einer Klasse ist, bekomme:

Task nicht serialisierbar: Java.io.NotSerializableException: testing

Das Problem ist, ich brauche meinen Code in einer Klasse und kein Objekt. Irgendeine Idee, warum das passiert? Ist ein Scala Objekt serialisiert (Standard?)?

Dies ist ein funktionierendes Codebeispiel:

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(_))
}

Dies ist das nicht funktionierende Beispiel:

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
}
207
Nimrod007

Ich denke nicht, dass die andere Antwort ganz richtig ist. RDDs sind in der Tat serialisierbar , das ist also nicht der Grund, warum Ihre Aufgabe fehlschlägt.

Spark ist eine verteilte Rechenmaschine und ihre Hauptabstraktion ist ein belastbares verteiltes Dataset ( RDD ), das als verteilte Sammlung betrachtet werden kann. Grundsätzlich sind die RDD-Elemente auf die Knoten des Clusters verteilt, aber Spark abstrahiert dies vom Benutzer und lässt den Benutzer mit der RDD (Auflistung) interagieren, als wäre es eine lokale.

Um nicht auf zu viele Details einzugehen, aber wenn Sie auf einem RDD (map, flatMap, filter und andere) verschiedene Transformationen ausführen, lautet Ihr Transformationscode (Abschluss):

  1. auf dem Treiberknoten serialisiert,
  2. an die entsprechenden Knoten im Cluster geliefert,
  3. deserialisiert,
  4. und schließlich auf den Knoten ausgeführt

Sie können dies natürlich lokal ausführen (wie in Ihrem Beispiel), aber alle diese Phasen (außer dem Versand über das Netzwerk) treten immer noch auf. [Auf diese Weise können Sie Fehler abfangen, noch bevor Sie sie in der Produktion einsetzen.]

In Ihrem zweiten Fall rufen Sie eine in der Klasse testing definierte Methode aus der Map-Funktion heraus auf. Spark sieht das und da Methoden nicht alleine serialisiert werden können, Spark versucht das Ganzetesting Klasse, damit der Code bei Ausführung in einer anderen JVM weiterhin funktioniert. Sie haben zwei Möglichkeiten:

Entweder Sie machen Klassentests serialisierbar, sodass die gesamte Klasse von Spark serialisiert werden kann:

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
}

oder Sie machen someFunc zu einer Funktion anstelle einer Methode (Funktionen sind Objekte in Scala), so dass Spark in der Lage ist, diese zu serialisieren:

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
}

Ähnliches, aber nicht dasselbe Problem mit der Klassenserialisierung kann für Sie von Interesse sein und Sie können es lesen in dieser Spark Summit 2013-Präsentation .

Als Randnotiz können Sie rddList.map(someFunc(_)) in rddList.map(someFunc) umschreiben, sie sind genau gleich. Normalerweise wird die zweite bevorzugt, da sie weniger ausführlich und übersichtlicher zu lesen ist.

EDIT (2015-03-15): SPARK-5307 eingeführt SerializationDebugger und Spark 1.3.0 ist die erste Version, die es verwendet. Es fügt einen Serialisierungspfad zu einer NotSerializableException hinzu. Wenn eine NotSerializableException auftritt, sucht der Debugger im Objektdiagramm nach dem Pfad zu dem Objekt, das nicht serialisiert werden kann, und erstellt Informationen, die dem Benutzer das Auffinden des Objekts erleichtern.

Im Fall von OP wird Folgendes auf stdout ausgegeben:

Serialization stack:
    - object not serializable (class: testing, value: [email protected])
    - field (class: testing$$anonfun$1, name: $outer, type: class testing)
    - object (class testing$$anonfun$1, <function1>)
302
Grega Kešpret

Gregas Antwort ist eine großartige Erklärung, warum der ursprüngliche Code nicht funktioniert, und zwei Möglichkeiten, das Problem zu beheben. Diese Lösung ist jedoch nicht sehr flexibel. Betrachten Sie den Fall, in dem Ihr Closure einen Methodenaufruf für eine Nicht - Serializable - Klasse enthält, über die Sie keine Kontrolle haben. Sie können dieser Klasse weder das Tag Serializable hinzufügen noch die zugrunde liegende Implementierung ändern, um die Methode in eine Funktion zu ändern.

Nilesh stellt eine großartige Lösung dafür dar, aber die Lösung kann sowohl prägnanter als auch allgemeiner gestaltet werden:

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

Dieser Funktions-Serializer kann dann verwendet werden, um Closures und Methodenaufrufe automatisch umzubrechen:

rdd map genMapper(someFunc)

Diese Technik hat auch den Vorteil, dass für den Zugriff auf KryoSerializationWrapper keine zusätzlichen Shark-Abhängigkeiten erforderlich sind, da Twitter's Chill bereits von Core Spark übernommen wird

31
Ben Sidhom

Vollständiger Vortrag, in dem das Problem vollständig erklärt wird. Dies ist ein guter Weg, um diese Serialisierungsprobleme zu vermeiden: https://github.com/samthebest/dump/blob/master/sams-scala-tutorial/serialization-exceptions- and-memory-leaks-no-ws.md

Die Antwort, die am häufigsten gewählt wurde, schlägt im Grunde vor, ein ganzes Sprachfeature wegzuwerfen - das verwendet keine Methoden mehr und nur noch Funktionen. In der funktionalen Programmierung sollten zwar Methoden in Klassen vermieden werden, aber die Umwandlung in Funktionen löst das Designproblem hier nicht (siehe obigen Link).

Als schnelle Lösung in dieser speziellen Situation können Sie einfach das @transient Anmerkung, die angibt, dass nicht versucht werden soll, den fehlerhaften Wert zu serialisieren (hier Spark.ctx ist eine benutzerdefinierte Klasse, die nicht nach Spark benannt ist:

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

Sie können den Code auch so umstrukturieren, dass rddList an einem anderen Ort lebt, aber das ist auch böse.

Die Zukunft ist wahrscheinlich Sporen

In Zukunft wird Scala) diese Dinge enthalten, die als "Sporen" bezeichnet werden und die es uns ermöglichen sollen, genau zu kontrollieren, was durch einen Verschluss eingezogen wird und was nicht. Außerdem sollten alle Fehler durch versehentliches Ziehen behoben werden Bei nicht serialisierbaren Typen (oder unerwünschten Werten) treten eher Kompilierungsfehler als jetzt auf, was fürchterliche Laufzeitausnahmen/Speicherlecks sind.

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

Ein Tipp zur Kryo-Serialisierung

Wenn Sie Kyro verwenden, stellen Sie sicher, dass eine Registrierung erforderlich ist. Dies bedeutet, dass Sie Fehler anstelle von Speicherlecks erhalten:

"Schließlich weiß ich, dass kryo kryo.setRegistrationOptional (true) hat, aber es fällt mir sehr schwer, herauszufinden, wie man es benutzt. Wenn diese Option aktiviert ist, scheint kryo immer noch Ausnahmen zu werfen, wenn ich mich nicht registriert habe Klassen."

Strategie zur Registrierung von Klassen bei kryo

Dies gibt Ihnen natürlich nur eine Kontrolle auf Typebene und keine Kontrolle auf Wertebene.

... weitere Ideen folgen.

25
samthebest

Ich hatte ein ähnliches Problem und was ich aus Gregas Antwort verstehe, ist

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

}

ihre doIT -Methode versucht, someFunc (_) -Methode zu serialisieren, aber da die Methode nicht serialisierbar ist, wird versucht, die Klasse zu serialisieren testing was wiederum nicht serialisierbar ist.

Damit Ihr Code funktioniert, sollten Sie someFunc in doIT definieren. Zum Beispiel:

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

Und wenn mehrere Funktionen ins Bild kommen, sollten alle diese Funktionen für den übergeordneten Kontext verfügbar sein.

8
Tarang Bhalodia

Ich habe dieses Problem mit einem anderen Ansatz gelöst. Sie müssen lediglich die Objekte serialisieren, bevor Sie den Verschluss passieren, und anschließend die Serialisierung aufheben. Dieser Ansatz funktioniert auch dann, wenn Ihre Klassen nicht serialisierbar sind, da Kryo hinter den Kulissen verwendet wird. Alles was Sie brauchen ist etwas Curry. ;)

Hier ist ein Beispiel, wie ich es gemacht habe:

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 }
}

Fühlen Sie sich frei, Blah so kompliziert zu machen, wie Sie möchten: Klasse, Begleitobjekt, verschachtelte Klassen, Verweise auf mehrere Bibliotheken von Drittanbietern.

KryoSerializationWrapper bezieht sich auf: https://github.com/amplab/shark/blob/master/src/main/scala/shark/execution/serialization/KryoSerializationWrapper.scala

8
Nilesh

Ich bin nicht ganz sicher, ob dies auf Scala) zutrifft, aber in Java habe ich die NotSerializableException durch Refactoring meines Codes gelöst, sodass die Schließung nicht auf eine nicht serialisierbare final Feld.

7
Trebor Rude