web-dev-qa-db-fra.com

Tâche non sérialisable: exception Java.io.NotSerializableException lors de l'appel d'une fonction en dehors de la fermeture uniquement sur des classes et non des objets

Avoir un comportement étrange lorsque vous appelez une fonction en dehors d’une fermeture:

  • quand la fonction est dans un objet tout fonctionne
  • quand function est dans une classe, obtenez:

Tâche non sérialisable: Java.io.NotSerializableException: testing

Le problème est que j'ai besoin de mon code dans une classe et non d'un objet. Une idée pourquoi cela se passe? Un objet Scala est-il sérialisé (par défaut?)?

Ceci est un exemple de code de travail:

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

Voici l'exemple non fonctionnel:

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

Je ne pense pas que l'autre réponse est tout à fait correcte. les RDD sont en effet sérialisables , ce n'est donc pas ce qui cause l'échec de votre tâche.

Spark est un moteur de calcul distribué dont l'abstraction principale est un ensemble de données résilient distribué (RDD), qui peut être visualisé sous la forme d'une collection distribuée. Fondamentalement, les éléments RDD sont partitionnés entre les nœuds du cluster, mais Spark l’abstrait de manière à ce que l’utilisateur puisse l’interagir, lui permettant d’interagir avec le RDD (collection) comme s’il s’agissait d’un local.

Sans entrer dans trop de détails, mais lorsque vous exécutez différentes transformations sur un RDD (map, flatMap, filter et autres), votre code de transformation (fermeture) est:

  1. sérialisé sur le nœud du pilote,
  2. expédiés aux nœuds appropriés du cluster,
  3. désérialisé,
  4. et enfin exécuté sur les noeuds

Vous pouvez bien sûr exécuter cela localement (comme dans votre exemple), mais toutes ces phases (à l'exception de l'expédition via le réseau) ont toujours lieu. [Cela vous permet d’attraper les bogues avant même le déploiement en production]

Dans votre deuxième cas, vous appelez une méthode, définie dans la classe testing, à l'intérieur de la fonction map. Spark voit cela et comme les méthodes ne peuvent pas être sérialisées par elles-mêmes, Spark essaie de sérialiser la classe entière testing, afin que le code fonctionne toujours lorsqu'il est exécuté dans une autre machine virtuelle Java. Vous avez deux possibilités:

Soit vous effectuez des tests de classe sérialisables, de sorte que toute la classe puisse être sérialisée par 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
}

ou vous créez la fonction someFunc à la place d'une méthode (les fonctions sont des objets dans Scala), de sorte que Spark puisse le sérialiser:

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
}

Un problème similaire, mais pas identique, avec la sérialisation de classe peut vous intéresser et vous pouvez lire dessus dans cette Spark Présentation du Sommet 201 .

En guise de remarque, vous pouvez réécrire rddList.map(someFunc(_)) à rddList.map(someFunc), elles sont exactement les mêmes. Habituellement, la seconde est préférable car elle est moins verbeuse et plus propre à lire.

EDIT (2015-03-15): SPARK-5307 introduit SerializationDebugger et Spark 1.3.0 est la première version à l'utiliser. Il ajoute un chemin de sérialisation à une exception NotSerializableException . Lorsqu'une exception NotSerializableException est rencontrée, le débogueur visite le graphe d'objet pour trouver le chemin d'accès à l'objet qui ne peut pas être sérialisé et construit des informations pour aider l'utilisateur à trouver l'objet.

Dans le cas de OP, voici ce qui sera imprimé sur 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>)
302
Grega Kešpret

La réponse de Grega explique parfaitement pourquoi le code original ne fonctionne pas et deux manières de résoudre le problème. Cependant, cette solution n’est pas très flexible; Considérez le cas où votre clôture inclut un appel de méthode sur une classe non -Serializable sur laquelle vous n'avez aucun contrôle. Vous ne pouvez ni ajouter la balise Serializable à cette classe, ni modifier l'implémentation sous-jacente pour transformer la méthode en une fonction.

Nilesh présente une excellente solution de contournement, mais la solution peut être plus concise et plus générale:

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

Ce sérialiseur de fonctions peut ensuite être utilisé pour envelopper automatiquement les fermetures et les appels de méthode:

rdd map genMapper(someFunc)

Cette technique présente également l’avantage de ne pas nécessiter de dépendances Shark supplémentaires pour pouvoir accéder à KryoSerializationWrapper, car le Chill de Twitter est déjà intégré dans le noyau Spark.

31
Ben Sidhom

Discussion complète expliquant complètement le problème, qui propose un excellent moyen de changer de paradigme pour éviter ces problèmes de sérialisation: https://github.com/samthebest/dump/blob/master/sams-scala-tutorial/serialization-exceptions- and-memory-leaks-no-ws.md

La réponse la plus votée suggère fondamentalement de supprimer une fonctionnalité de langue entière - qui n’utilise plus de méthodes, mais uniquement des fonctions. En effet, les méthodes de programmation fonctionnelle dans les classes devraient être évitées, mais les transformer en fonctions ne résout pas le problème de conception ici (voir le lien ci-dessus).

Comme solution rapide dans cette situation particulière, vous pouvez simplement utiliser l'annotation @transient pour lui dire de ne pas essayer de sérialiser la valeur incriminée (ici, Spark.ctx est une classe personnalisée et non celle de Spark après la dénomination de l'OP):

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

Vous pouvez également restructurer le code pour que rddList vive ailleurs, mais c'est aussi désagréable.

L'avenir est probablement des spores

À l'avenir, Scala inclura ces éléments appelés "spores" qui devraient nous permettre de contrôler avec précision le grain des produits qui sont ou ne sont pas exactement entraînés par une fermeture. En outre, cela devrait transformer toutes les erreurs d’insertion accidentelle de types non sérialisables (ou de valeurs non désirées) en erreurs de compilation plutôt qu’aujourd’hui d’épouvantables exceptions d’exécution/de fuites de mémoire.

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

Un conseil sur la sérialisation en kryo

Lorsque vous utilisez kyro, assurez-vous que l'enregistrement est nécessaire, cela signifie que vous obtiendrez des erreurs au lieu des fuites de mémoire:

"Enfin, je sais que kryo a kryo.setRegistrationOptional (true) mais j'ai beaucoup de difficulté à essayer de comprendre comment l'utiliser. Lorsque cette option est activée, kryo semble toujours lancer des exceptions si je ne me suis pas inscrit Des classes."

Stratégie d'enregistrement des cours avec kryo

Bien sûr, cela ne vous donne que le contrôle de niveau type, pas le contrôle de niveau valeur.

... plus d'idées à venir.

25
samthebest

Je suis confronté à un problème similaire, et ce que je comprends de réponse de Grega est

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

}

votre méthode doIT tente de sérialiser une méthode someFunc (_) , mais comme méthode ne sont pas sérialisables, il essaie de sérialiser la classe en testant , ce qui n’est pas encore sérialisable.

Donc, pour que votre code fonctionne, vous devez définir someFunc dans la méthode doIT . Par exemple:

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

Et si plusieurs fonctions apparaissent dans l'image, toutes ces fonctions doivent être disponibles pour le contexte parent.

8
Tarang Bhalodia

J'ai résolu ce problème en utilisant une approche différente. Vous devez simplement sérialiser les objets avant de passer par la fermeture et désérialiser par la suite. Cette approche fonctionne, même si vos classes ne sont pas sérialisables, car elle utilise le Kryo en arrière-plan. Tout ce dont vous avez besoin, c'est du curry. ;)

Voici un exemple de comment je l'ai fait:

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

N'hésitez pas à rendre Blah aussi compliqué que vous le souhaitez, classe, objet compagnon, classes imbriquées, références à plusieurs bibliothèques tierces.

KryoSerializationWrapper fait référence à: https://github.com/amplab/shark/blob/master/src/main/scala/shark/execution/serialization/KryoSerializationWrapper.scala

8
Nilesh

Je ne suis pas tout à fait sûr que cela s'applique à Scala mais, en Java, j'ai résolu le NotSerializableException en refacturant mon code afin que la fermeture n'accède pas à un final non sérialisable _ champ.

7
Trebor Rude