Attività non serializzabile: java.io.NotSerializableException quando si chiama la funzione fuori dalla chiusura solo su classi non oggetti


224

Ottenere un comportamento strano quando si chiama la funzione al di fuori di una chiusura:

  • quando la funzione è in un oggetto tutto funziona
  • quando la funzione è in una classe ottenere:

Attività non serializzabile: java.io.NotSerializableException: testing

Il problema è che ho bisogno del mio codice in una classe e non in un oggetto. Qualche idea sul perché questo stia accadendo? Un oggetto Scala è serializzato (impostazione predefinita?)?

Questo è un esempio di codice funzionante:

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

Questo è l'esempio non funzionante:

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
}

Che cos'è Spark.ctx? Non esiste alcun oggetto Spark con metodo ctx AFAICT
javadba

Risposte:


334

I RDD estendono l'interfaccia Serializzabile , quindi questo non è ciò che causa il fallimento dell'attività . Questo non significa che è possibile serializzare un RDDcon Spark ed evitareNotSerializableException

Spark è un motore di elaborazione distribuito e la sua principale astrazione è un set di dati distribuito resiliente ( RDD ), che può essere visualizzato come una raccolta distribuita. Fondamentalmente, gli elementi di RDD sono partizionati attraverso i nodi del cluster, ma Spark lo estrae dall'utente, permettendo all'utente di interagire con il RDD (raccolta) come se fosse locale.

Non entrare in troppi dettagli, ma quando esegui trasformazioni diverse su un RDD ( map,flatMap , filtere altri), il codice di trasformazione (chiusura) è:

  1. serializzato sul nodo del driver,
  2. spedito ai nodi appropriati nel cluster,
  3. deserializzati,
  4. e infine eseguito sui nodi

Ovviamente puoi eseguirlo localmente (come nel tuo esempio), ma tutte quelle fasi (ad eccezione della spedizione in rete) si verificano comunque. [Ciò consente di rilevare eventuali bug anche prima di implementarli in produzione]

Quello che succede nel secondo caso è che stai chiamando un metodo, definito in classe testingdall'interno della funzione map. Spark vede che e poiché i metodi non possono essere serializzati da soli, Spark tenta di serializzare l'intera testing classe, in modo che il codice continuerà a funzionare quando eseguito in un'altra JVM. Hai due possibilità:

O rendi serializzabile il test di classe, quindi l'intera classe può essere serializzata da 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
}

oppure fai la someFuncfunzione invece di un metodo (le funzioni sono oggetti in Scala), così Spark sarà in grado di serializzarlo:

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
}

Simile, ma non lo stesso problema con la serializzazione di classe può essere di tuo interesse e puoi leggerlo in questa presentazione di Spark Summit 2013 .

Come nota a margine, puoi riscrivere rddList.map(someFunc(_)) a rddList.map(someFunc), essi sono esattamente gli stessi. Di solito, il secondo è preferito in quanto è meno dettagliato e più pulito da leggere.

EDIT (2015-03-15): SPARK-5307 ha introdotto SerializationDebugger e Spark 1.3.0 è la prima versione a usarlo. Aggiunge il percorso di serializzazione a NotSerializableException . Quando viene rilevata NotSerializableException, il debugger visita il grafico dell'oggetto per trovare il percorso verso l'oggetto che non può essere serializzato e costruisce informazioni per aiutare l'utente a trovare l'oggetto.

Nel caso di OP, questo è ciò che viene stampato su 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
Hmm, quello che hai spiegato ha sicuramente senso e spiega perché l'intera classe viene serializzata (qualcosa che non ho capito del tutto). Tuttavia, continuerò a ritenere che i rdd non siano serializzabili (bene estendono Serializable, ma ciò non significa che non causino NotSerializableException, provalo). Questo è il motivo per cui se li metti fuori dalle classi corregge l'errore. Vado a modificare un po 'la mia risposta per essere più precisi su ciò che intendo - cioè causano l'eccezione, non che estendono l'interfaccia.
Samthebest,

35
Nel caso in cui non hai il controllo sulla classe, devi essere serializzabile ... se stai usando Scala puoi semplicemente crearne un'istanza con Serializable:val test = new Test with Serializable
Segna l'

4
"rddList.map (someFunc (_)) a rddList.map (someFunc), sono esattamente gli stessi" No, non sono esattamente gli stessi, e in effetti l'uso di quest'ultimo può causare eccezioni di serializzazione come il primo no.
Samthebest,

1
@samthebest potresti spiegare per favore perché map (someFunc (_)) non causerebbe eccezioni di serializzazione mentre map (someFunc) lo farebbe?
Alon,

31

La risposta di Grega è ottima per spiegare perché il codice originale non funziona e due modi per risolvere il problema. Tuttavia, questa soluzione non è molto flessibile; considera il caso in cui la tua chiusura includa una chiamata di metodo su una non Serializableclasse su cui non hai controllo. Non è possibile aggiungere il Serializabletag a questa classe né modificare l'implementazione sottostante per modificare il metodo in una funzione.

Nilesh presenta un'ottima soluzione per questo, ma la soluzione può essere resa sia più concisa che generale:

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

Questo serializzatore di funzioni può quindi essere utilizzato per avvolgere automaticamente chiusure e chiamate di metodo:

rdd map genMapper(someFunc)

Questa tecnica ha anche il vantaggio di non richiedere le dipendenze aggiuntive dello squalo per poter accedere KryoSerializationWrapper, dal momento che Chill di Twitter è già inserito nel core Spark


Ciao, mi chiedo se devo usare qualcosa per registrare qualcosa? Ho provato a ottenere un'eccezione Impossibile trovare la classe da Kryo. THX
G_cy

25

Intervento completo che spiega completamente il problema, che propone un ottimo modo di cambiare paradigma per evitare questi problemi di serializzazione: https://github.com/samthebest/dump/blob/master/sams-scala-tutorial/serialization-exceptions-and-memory- leaks-no-ws.md

La risposta più votata suggerisce sostanzialmente di gettare via un'intera funzione linguistica, che non utilizza più metodi e utilizza solo funzioni. Infatti nei metodi di programmazione funzionale nelle classi dovrebbe essere evitato, ma trasformarli in funzioni non risolve qui il problema di progettazione (vedi link sopra).

Come soluzione rapida in questa particolare situazione potresti semplicemente usare l' @transientannotazione per dirgli di non provare a serializzare il valore offensivo (qui, Spark.ctxè una classe personalizzata non quella di Spark che segue la denominazione dell'OP):

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

Puoi anche ristrutturare il codice in modo che rddList risieda altrove, ma è anche brutto.

Il futuro è probabilmente spore

In futuro Scala includerà queste cose chiamate "spore" che dovrebbero permetterci di controllare con precisione il grano che cosa fa e non viene tirato esattamente da una chiusura. Inoltre, questo dovrebbe trasformare tutti gli errori di estrazione accidentale di tipi non serializzabili (o di qualsiasi valore indesiderato) in errori di compilazione piuttosto che ora, che sono orribili eccezioni di runtime / perdite di memoria.

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

Un consiglio sulla serializzazione di Kryo

Quando si utilizza kyro, assicurarsi che sia necessaria la registrazione, ciò significa che si ottengono errori anziché perdite di memoria:

"Infine, so che kryo ha kryo.setRegistrationOptional (true) ma sto avendo un momento molto difficile cercando di capire come usarlo. Quando questa opzione è attiva, Kryo sembra ancora lanciare eccezioni se non mi sono registrato classi."

Strategia per la registrazione di classi con Kryo

Naturalmente questo ti dà solo il controllo a livello di tipo e non il controllo a livello di valore.

... altre idee a venire.


9

Ho risolto questo problema usando un approccio diverso. È sufficiente serializzare gli oggetti prima di passare attraverso la chiusura e successivamente deserializzare. Questo approccio funziona, anche se le tue lezioni non sono serializzabili, perché usa Kryo dietro le quinte. Tutto ciò che serve è un po 'di curry. ;)

Ecco un esempio di come l'ho fatto:

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

Sentiti libero di rendere Blah complicato quanto vuoi, classe, oggetto associato, classi nidificate, riferimenti a più librerie di terze parti.

KryoSerializationWrapper si riferisce a: https://github.com/amplab/shark/blob/master/src/main/scala/shark/execution/serialization/KryoSerializationWrapper.scala


Questo effettivamente serializza l'istanza o crea un'istanza statica e serializza un riferimento (vedi la mia risposta).
Samthebest

2
@samthebest potresti elaborare? Se indaghi KryoSerializationWrapper, scoprirai che Spark pensa che sia davvero java.io.Serializable- serializza semplicemente l'oggetto internamente usando Kryo - più veloce, più semplice. E non penso che abbia a che fare con un'istanza statica: deserializza semplicemente il valore quando viene chiamato value.apply ().
Nilesh,

8

Ho affrontato un problema simile e quello che ho capito dalla risposta di Grega è

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

}

il tuo metodo doIT sta tentando di serializzare il metodo someFunc (_) , ma poiché il metodo non è serializzabile, prova a serializzare i test di classe che non sono nuovamente serializzabili.

Quindi fai funzionare il tuo codice, dovresti definire someFunc all'interno del metodo doIT . Per esempio:

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

E se ci sono più funzioni che appaiono in figura, allora tutte quelle funzioni dovrebbero essere disponibili per il contesto genitore.


7

Non sono del tutto sicuro che questo si applichi a Scala, ma, in Java, ho risolto il NotSerializableExceptionrefactoring del mio codice in modo che la chiusura non accedesse a un finalcampo non serializzabile .


sto affrontando lo stesso problema in Java, sto cercando di utilizzare la classe FileWriter dal pacchetto IO Java all'interno del metodo foreach RDD. Potete per favore fatemi sapere come possiamo risolvere questo.
Shankar,

1
Bene @Shankar, se FileWriterc'è un finalcampo della classe esterna, non puoi farlo. Ma FileWriterpuò essere costruito da a Stringo a File, entrambi i quali lo sono Serializable. Quindi refatta il tuo codice per costruire un locale FileWriterbasato sul nome file della classe esterna.
Trebor Rude,

0

Cordiali saluti in Spark 2.4 molti di voi probabilmente incontreranno questo problema. La serializzazione di Kryo è migliorata ma in molti casi non è possibile utilizzare spark.kryo.unsafe = true o l'ingenuo serializzatore di Kryo.

Per una soluzione rapida prova a modificare quanto segue nella tua configurazione Spark

spark.kryo.unsafe="false"

O

spark.serializer="org.apache.spark.serializer.JavaSerializer"

Modifico le trasformazioni RDD personalizzate che incontro o scrivo personalmente utilizzando esplicite variabili di trasmissione e utilizzando la nuova API integrata di twitter-chill, convertendole da rdd.map(row =>in rdd.mapPartitions(partition => {funzioni.

Esempio

Vecchio (non eccezionale) Modo

val sampleMap = Map("index1" -> 1234, "index2" -> 2345)
val outputRDD = rdd.map(row => {
    val value = sampleMap.get(row._1)
    value
})

Modo alternativo (migliore)

import com.twitter.chill.MeatLocker
val sampleMap = Map("index1" -> 1234, "index2" -> 2345)
val brdSerSampleMap = spark.sparkContext.broadcast(MeatLocker(sampleMap))

rdd.mapPartitions(partition => {
    val deSerSampleMap = brdSerSampleMap.value.get
    partition.map(row => {
        val value = sampleMap.get(row._1)
        value
    }).toIterator
})

Questo nuovo modo chiamerà la variabile broadcast solo una volta per partizione, il che è meglio. Sarà comunque necessario utilizzare la serializzazione Java se non si registrano le classi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.