web-dev-qa-db-fra.com

java.lang.ClassCastException utilisant des expressions lambda dans un travail d'étincelle sur un serveur distant

J'essaie de créer une API Web pour mes jobs d'étincelle Apache à l'aide du framework sparkjava.com. Mon code est:

@Override
public void init() {
    get("/hello",
            (req, res) -> {
                String sourcePath = "hdfs://spark:54310/input/*";

                SparkConf conf = new SparkConf().setAppName("LineCount");
                conf.setJars(new String[] { "/home/sam/resin-4.0.42/webapps/test.war" });
                File configFile = new File("config.properties");

                String sparkURI = "spark://hamrah:7077";

                conf.setMaster(sparkURI);
                conf.set("spark.driver.allowMultipleContexts", "true");
                JavaSparkContext sc = new JavaSparkContext(conf);

                @SuppressWarnings("resource")
                JavaRDD<String> log = sc.textFile(sourcePath);

                JavaRDD<String> lines = log.filter(x -> {
                    return true;
                });

                return lines.count();
            });
}

Si je supprime l'expression lambda ou la mets dans un simple fichier jar plutôt que dans un service Web (en quelque sorte une servlet), il s'exécutera sans erreur. Mais l'utilisation d'une expression lambda dans une servlet entraînera cette exception:

15/01/28 10:36:33 WARN TaskSetManager: Lost task 0.0 in stage 0.0 (TID 0, hamrah): Java.lang.ClassCastException: cannot assign instance of Java.lang.invoke.SerializedLambda to field org.Apache.spark.api.Java.JavaRDD$$anonfun$filter$1.f$1 of type org.Apache.spark.api.Java.function.Function in instance of org.Apache.spark.api.Java.JavaRDD$$anonfun$filter$1
at Java.io.ObjectStreamClass$FieldReflector.setObjFieldValues(ObjectStreamClass.Java:2089)
at Java.io.ObjectStreamClass.setObjFieldValues(ObjectStreamClass.Java:1261)
at Java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.Java:1999)
at Java.io.ObjectInputStream.readSerialData(ObjectInputStream.Java:1918)
at Java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.Java:1801)
at Java.io.ObjectInputStream.readObject0(ObjectInputStream.Java:1351)
at Java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.Java:1993)
at Java.io.ObjectInputStream.readSerialData(ObjectInputStream.Java:1918)
at Java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.Java:1801)
at Java.io.ObjectInputStream.readObject0(ObjectInputStream.Java:1351)
at Java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.Java:1993)
at Java.io.ObjectInputStream.readSerialData(ObjectInputStream.Java:1918)
at Java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.Java:1801)
at Java.io.ObjectInputStream.readObject0(ObjectInputStream.Java:1351)
at Java.io.ObjectInputStream.readObject(ObjectInputStream.Java:371)
at org.Apache.spark.serializer.JavaDeserializationStream.readObject(JavaSerializer.scala:62)
at org.Apache.spark.serializer.JavaSerializerInstance.deserialize(JavaSerializer.scala:87)
at org.Apache.spark.scheduler.ResultTask.runTask(ResultTask.scala:57)
at org.Apache.spark.scheduler.Task.run(Task.scala:56)
at org.Apache.spark.executor.Executor$TaskRunner.run(Executor.scala:196)
at Java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.Java:1142)
at Java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.Java:617)
at Java.lang.Thread.run(Thread.Java:745)

P.S: J'ai essayé la combinaison du jersey et du javaspark avec la jetée, le Tomcat et la résine et ils m'ont tous conduit au même résultat.

20
Mehraban

Ce que vous avez ici est une erreur de suivi qui masque l'erreur d'origine.

Lorsque les instances lambda sont sérialisées, elles utilisent writeReplace pour dissoudre leur implémentation Spécifique à JRE du formulaire persistant qui est une instance SerializedLambda . Lorsque l'instance SerializedLambda a été restaurée, sa méthode readResolve sera invoquée Pour reconstituer l'instance lambda appropriée. Comme le dit la documentation, il le fera en appelant une méthode spéciale de la classe qui définit le lambda original (voir aussi cette réponse ). Le point important est que la classe d’origine est nécessaire et que c’est ce qui manque dans votre cas.

Mais il y a un… comportement… particulier… de la ObjectInputStream. Lorsqu'il rencontre une exception, il ne se libère pas immédiatement. Il enregistrera l'exception et poursuivra le processus, en marquant tous les objets en cours de lecture, ce qui dépendra également de l'objet erroné. Ce n'est qu'à la fin du processus qu'il lève l'exception originale rencontrée. Ce qui le rend si étrange, c'est qu'il continuera également d'essayer de définir les champs de ces objets. Mais quand vous regardez la méthode ObjectInputStream.readOrdinaryObject ligne 1806:

…
    if (obj != null &&
        handles.lookupException(passHandle) == null &&
        desc.hasReadResolveMethod())
    {
        Object rep = desc.invokeReadResolve(obj);
        if (unshared && rep.getClass().isArray()) {
            rep = cloneArray(rep);
        }
        if (rep != obj) {
            handles.setObject(passHandle, obj = rep);
        }
    }

    return obj;
}

vous voyez qu’elle n’appelle pas la méthode readResolve lorsque lookupException signale une exception non -null. Mais lorsque la substitution n’est pas arrivée, ce n’est pas une bonne idée de continuer à essayer de définir les valeurs de champ du référent, mais c’est exactement ce qui se passe ici, produisant ainsi une ClassCastException.

Vous pouvez facilement reproduire le problème:

public class Holder implements Serializable {
    Runnable r;
}
public class Defining {
    public static Holder get() {
        final Holder holder = new Holder();
        holder.r=(Runnable&Serializable)()->{};
        return holder;
    }
}
public class Writing {
    static final File f=new File(System.getProperty("Java.io.tmpdir"), "x.ser");
    public static void main(String... arg) throws IOException {
        try(FileOutputStream os=new FileOutputStream(f);
            ObjectOutputStream   oos=new ObjectOutputStream(os)) {
            oos.writeObject(Defining.get());
        }
        System.out.println("written to "+f);
    }
}
public class Reading {
    static final File f=new File(System.getProperty("Java.io.tmpdir"), "x.ser");
    public static void main(String... arg) throws IOException, ClassNotFoundException {
        try(FileInputStream is=new FileInputStream(f);
            ObjectInputStream ois=new ObjectInputStream(is)) {
            Holder h=(Holder)ois.readObject();
            System.out.println(h.r);
            h.r.run();
        }
        System.out.println("read from "+f);
    }
}

Compilez ces quatre classes et exécutez Writing. Supprimez ensuite le fichier de classe Defining.class et exécutez Reading. Ensuite, vous obtiendrez un

Exception in thread "main" Java.lang.ClassCastException: cannot assign instance of Java.lang.invoke.SerializedLambda to field test.Holder.r of type Java.lang.Runnable in instance of test.Holder
    at Java.io.ObjectStreamClass$FieldReflector.setObjFieldValues(ObjectStreamClass.Java:2089)
    at Java.io.ObjectStreamClass.setObjFieldValues(ObjectStreamClass.Java:1261)

(Testé avec 1.8.0_20)


En fin de compte, vous pouvez oublier ce problème de sérialisation une fois que vous avez compris ce qui se passe. Pour résoudre votre problème, il vous suffit de vous assurer que la classe qui a défini l'expression lambda est également disponible dans l'exécution où le lambda est désérialisé.

Exemple d'exécution d'un travail d'étincelle directement à partir de IDE (spark-submit distribue le fichier jar par défaut):

SparkConf sconf = new SparkConf()
  .set("spark.eventLog.dir", "hdfs://nn:8020/user/spark/applicationHistory")
  .set("spark.eventLog.enabled", "true")
  .setJars(new String[]{"/path/to/jar/with/your/class.jar"})
  .setMaster("spark://spark.standalone.uri:7077");
41
Holger

J'ai eu la même erreur et j'ai remplacé le lambda par une classe interne, puis cela a fonctionné. Je ne comprends pas vraiment pourquoi, et reproduire cette erreur était extrêmement difficile (nous avions un serveur qui présentait le comportement, et nulle part ailleurs).

Provoque des problèmes de sérialisation (utilise lambdas, provoque l'erreur SerializedLambda)

this.variable = () -> { ..... }

Rendement Java.lang.ClassCastException: cannot assign instance of Java.lang.invoke.SerializedLambda to field MyObject.val$variable

Travaux

this.variable = new MyInterface() {
    public void myMethod() {
       .....
    }
};
2
Adrian Smith

Je suppose que votre problème est un échec de la boxe automatique. Dans le code

x -> {
      return true;
}

vous passez (String->boolean) lambda (c'est Predicate<String>) tandis que méthode de filtrage prend (String->Boolean) lambda (c'est Function<String,Boolean>). Je vous propose donc de changer le code en 

x -> {
      return Boolean.TRUE;
}

Inclure des détails dans votre question s'il vous plaît. Les sorties de uname -a et Java -version sont appréciées. Fournissez sscce si possible.

2
Sergey Fedorov

Vous pouvez peut-être plus simplement remplacer votre lambda Java8 par un spark.scala.Function

remplacer

output = rdds.map(x->this.function(x)).collect()

avec:

output = rdds.map(new Function<Double,Double>(){

   public Double call(Double x){
       return MyClass.this.function(x);
   }

}).collect();
0
Nicolas Zozol