41 votes

Résolution des problèmes de dépendance dans Apache Spark

Les problèmes courants lors de la création et du déploiement d'applications Spark sont les suivants:

  • java.lang.ClassNotFoundException .
  • object x is not a member of package y erreurs de compilation.
  • java.lang.NoSuchMethodError

Comment peut-on les résoudre?

34voto

Tzach Zohar Points 6701

Apache Spark classpath de l'est construit dynamiquement (à adapter par l'utilisateur de l'application du code) ce qui la rend vulnérable à de telles questions. @user7337271's réponse est correcte, mais il y a d'autres préoccupations, selon le gestionnaire du cluster ("master") que vous utilisez.

Tout d'abord, une Étincelle de l'application se compose des éléments suivants (chacun est séparé de la JVM, donc potentiellement contient des classes différentes dans son classpath):

  1. Pilote: c'est votre demande de création d'un SparkSession (ou SparkContext) et la connexion à un cluster manager pour effectuer le travail réel
  2. Le Gestionnaire de Cluster: sert de "point d'entrée" pour le cluster, en charge de l'allocation des exécuteurs pour chaque application. Il ya plusieurs différents types de prise en charge dans Spark: autonome, de FILÉS et de Mesos, que nous allons décrire ci-dessous.
  3. Les exécuteurs: ce sont les processus sur les nœuds du cluster, en effectuant le travail réel (l'exécution de l'Étincelle tâches)

Le relationsip entre elles est décrite dans ce diagramme de Apache Spark du cluster en mode aperçu:

Cluster Mode Overview

Maintenant - les classes à résider dans chacune de ces composantes?

Cela peut être répondu par le diagramme suivant:

Class placement overview

Nous allons analyser que lentement:

  1. Spark Code sont Étincelle des bibliothèques. Ils doivent exister dans TOUS les trois composantes comprennent la colle qui permettent d'Allumage de réaliser la communication entre eux. Par la voie d'Allumage auteurs ont fait une décision de conception pour inclure le code pour TOUS les composants TOUS les composants (par exemple, pour inclure le code qui doit s'exécuter uniquement dans Exécuteur testamentaire de pilote de trop) pour simplifier ce - de sorte que l'Étincelle du "gras bocal" (dans les versions jusqu'à 1,6), ou "archive" (dans la version 2.0, les détails ci-dessous) contient le code nécessaire pour tous les composants et devrait être disponible dans tous les d'entre eux.

  2. Pilote-Seul le Code c'est le code de l'utilisateur qui ne comprend pas ce qui doit être utilisée sur les Exécuteurs testamentaires, c'est à dire un code qui n'est pas utilisée dans toutes les transformations des RDD / DataFrame / Dataset. Ce n'est pas forcément à se séparer de l'distribué le code de l'utilisateur, mais il peut l'être.

  3. Distribué Code c'est le code de l'utilisateur qui est compilé avec le code de pilote, mais aussi doit être exécuté sur les Exécuteurs testamentaires - tout ce que le réel transformations doivent être inclus dans ce pot(s).

Maintenant que nous avons obtenu que la droite, comment pouvons-nous obtenir les classes à charger correctement dans chaque composante, et quelles sont les règles à suivre?

  1. Spark Code: comme les réponses précédentes de l'état, vous devez utiliser le même Scala et Étincelle versions dans toutes les composantes.

    1.1 Autonome de mode, il y a une "pré-existants" l'Étincelle de l'installation pour les applications (les pilotes) peuvent se connecter. Cela signifie que tous les pilotes doivent utiliser la même Étincelle de la version en cours d'exécution sur le maître et les exécuteurs.

    1.2 En FIL / Mesos, chaque application peut utiliser une autre Étincelle version, mais tous les composants de la même application doit utiliser le même. Cela signifie que si vous avez utilisé la version X de compiler et d'emballage de votre pilote de l'application, vous devez fournir la même version lors du démarrage de l'SparkSession (par ex. via l' spark.yarn.archive ou spark.yarn.jars paramètres lors de l'utilisation de FIL). Les pots / archive que vous fournissez doit inclure tous Étincelle dépendances (y compris les dépendances transitives), et il sera expédié par le gestionnaire du cluster de chaque exécuteur lorsque l'application démarre.

  2. Code du pilote: c'est entièrement de pilote de code peut être expédié comme un tas de pots ou de "gras bocal", tant qu'il inclut tous Étincelle dépendances + tous le code de l'utilisateur

  3. Distribué Code: en plus d'être présent sur le Conducteur, ce code doit être expédiée à exécuteurs testamentaires (encore une fois, avec l'ensemble de ses dépendances transitives). Ceci est fait en utilisant l' spark.jars paramètre.

Pour résumer, voici une proposition de démarche pour la construction et le déploiement d'une Application Spark (dans ce cas - à l'aide de FIL):

  • Créer une bibliothèque avec votre code, tout à la fois comme un "régulier" pot (avec une .pom fichier décrivant ses dépendances) et comme un "gros bocal" (avec toutes ses dépendances transitives inclus).
  • La création d'un pilote de l'application, avec la compilation des dépendances sur votre distribués code de la bibliothèque et sur Apache Spark (avec une version spécifique)
  • Package de l'application chauffeur dans un gros bocal à être déployé à pilote
  • Passer de la version de votre code en tant que valeur de spark.jars paramètre lors du démarrage de l' SparkSession
  • Passer à l'emplacement d'un fichier d'archive (par exemple, gzip) contenant tous les pots en vertu de l' lib/ le dossier téléchargé Étincelle binaires est la valeur de spark.yarn.archive

22voto

user7337271 Points 672

Lors de la construction et le déploiement d'Étincelle applications toutes les dépendances nécessitent des versions compatibles.

  • Scala version. Tous les paquets ont pour utiliser les mêmes grandes (2.10, 2.11, 2.12) Scala version.

    Considérez ce qui suit (incorrect) build.sbt:

    name := "Simple Project"
    
    version := "1.0"
    
    libraryDependencies ++= Seq(
       "org.apache.spark" % "spark-core_2.11" % "2.0.1",
       "org.apache.spark" % "spark-streaming_2.10" % "2.0.1",
       "org.apache.bahir" % "spark-streaming-twitter_2.11" % "2.0.1"
    )
    

    Nous utilisons spark-streaming pour Scala 2.10 tandis que les paquets restants sont pour Scala 2.11. Un valide fichier peut être

    name := "Simple Project"
    
    version := "1.0"
    
    libraryDependencies ++= Seq(
       "org.apache.spark" % "spark-core_2.11" % "2.0.1",
       "org.apache.spark" % "spark-streaming_2.11" % "2.0.1",
       "org.apache.bahir" % "spark-streaming-twitter_2.11" % "2.0.1"
    )
    

    mais il est préférable de spécifier la version globale et d'utiliser des %%:

    name := "Simple Project"
    
    version := "1.0"
    
    scalaVersion := "2.11.7"
    
    libraryDependencies ++= Seq(
       "org.apache.spark" %% "spark-core" % "2.0.1",
       "org.apache.spark" %% "spark-streaming" % "2.0.1",
       "org.apache.bahir" %% "spark-streaming-twitter" % "2.0.1"
    )
    

    De même, dans Maven:

    <project>
      <groupId>com.example</groupId>
      <artifactId>simple-project</artifactId>
      <modelVersion>4.0.0</modelVersion>
      <name>Simple Project</name>
      <packaging>jar</packaging>
      <version>1.0</version>
      <properties>
        <spark.version>2.0.1</spark.version>
      </properties> 
      <dependencies>
        <dependency> <!-- Spark dependency -->
          <groupId>org.apache.spark</groupId>
          <artifactId>spark-core_2.11</artifactId>
          <version>${spark.version}</version>
        </dependency>
        <dependency>
          <groupId>org.apache.spark</groupId>
          <artifactId>spark-streaming_2.11</artifactId>
          <version>${spark.version}</version>
        </dependency> 
        <dependency>
          <groupId>org.apache.bahir</groupId>
          <artifactId>spark-streaming-twitter_2.11</artifactId>
          <version>${spark.version}</version>
        </dependency>
      </dependencies>
    </project>
    
  • Spark version Tous les paquets doivent utiliser les mêmes grandes Spark version (1.6, 2.0, 2.1, ...).

    Considérez ce qui suit (incorrect) construire.sbt:

    name := "Simple Project"
    
    version := "1.0"
    
    libraryDependencies ++= Seq(
       "org.apache.spark" % "spark-core_2.11" % "1.6.1",
       "org.apache.spark" % "spark-streaming_2.10" % "2.0.1",
       "org.apache.bahir" % "spark-streaming-twitter_2.11" % "2.0.1"
    )
    

    Nous utilisons spark-core 1.6 tandis que les autres composants sont Spark 2.0. Un valide fichier peut être

    name := "Simple Project"
    
    version := "1.0"
    
    libraryDependencies ++= Seq(
       "org.apache.spark" % "spark-core_2.11" % "2.0.1",
       "org.apache.spark" % "spark-streaming_2.10" % "2.0.1",
       "org.apache.bahir" % "spark-streaming-twitter_2.11" % "2.0.1"
    )
    

    mais il est préférable d'utiliser une variable:

    name := "Simple Project"
    
    version := "1.0"
    
    val sparkVersion = "2.0.1"
    
    libraryDependencies ++= Seq(
       "org.apache.spark" % "spark-core_2.11" % sparkVersion,
       "org.apache.spark" % "spark-streaming_2.10" % sparkVersion,
       "org.apache.bahir" % "spark-streaming-twitter_2.11" % sparkVersion
    )
    

    De même, dans Maven:

    <project>
      <groupId>com.example</groupId>
      <artifactId>simple-project</artifactId>
      <modelVersion>4.0.0</modelVersion>
      <name>Simple Project</name>
      <packaging>jar</packaging>
      <version>1.0</version>
      <properties>
        <spark.version>2.0.1</spark.version>
        <scala.version>2.11</scala.version>
      </properties> 
      <dependencies>
        <dependency> <!-- Spark dependency -->
          <groupId>org.apache.spark</groupId>
          <artifactId>spark-core_${scala.version}</artifactId>
          <version>${spark.version}</version>
        </dependency>
        <dependency>
          <groupId>org.apache.spark</groupId>
          <artifactId>spark-streaming_${scala.version}</artifactId>
          <version>${spark.version}</version>
        </dependency> 
        <dependency>
          <groupId>org.apache.bahir</groupId>
          <artifactId>spark-streaming-twitter_${scala.version}</artifactId>
          <version>${spark.version}</version>
        </dependency>
      </dependencies>
    </project>
    
  • Spark version utilisée dans Spark dépendances doit correspondre Spark version de l'Étincelle de l'installation. Par exemple, si vous utilisez 1.6.1 sur le cluster, vous devez utiliser 1.6.1 pour construire des bocaux. Les versions mineures d'incompatibilité ne sont pas toujours acceptés.

  • Scala version utilisée pour construire pot doit correspondre à la Scala de version utilisé pour construire déployé Étincelle. Par défaut (à télécharger les binaires par défaut et construit):

    • Spark 1.x -> Scala 2.10
    • Spark 2.x -> Scala 2.11
  • Les paquets supplémentaires devraient être accessibles sur les nœuds de travail si elle est incluse dans le gros pot. Il existe un certain nombre d'options, y compris:

    • --jars argument pour spark-submit - pour distribuer localement jar fichiers.
    • --packages argument pour spark-submit - pour extraire les dépendances de Maven repository.

    Lors de la soumission dans le nœud de cluster, vous devez inclure l'application jar en --jars.

3voto

winson Points 53

En plus de la très vaste réponse déjà donnée par user7337271, si le problème résulte d'un manque de dépendances externes, vous pouvez construire un bocal avec vos dépendances avec, par exemple, maven assembly plugin

Dans ce cas, assurez-vous de marquer tous les core étincelle dépendances comme "prévu" dans votre système de construction et, comme l'a déjà noté, assurez-vous qu'ils sont en corrélation avec votre runtime spark version.

2voto

user7344209 Points 179

La dépendance classes de votre application doit être spécifié dans l' application jar option de votre lancement de la commande.

Plus de détails peuvent être trouvés à l' Étincelle de la documentation

Prises à partir de la documentation:

l'application jar: Chemin d'accès à un groupés pot, y compris votre demande et toutes les dépendances. L'URL doit être visible au niveau mondial à l'intérieur de votre cluster, par exemple, un hdfs:// chemin d'accès ou un fichier:// chemin d'accès qui est présent sur tous les nœuds

0voto

dmitrybugakov Points 1

Je pense que ce problème doit résoudre une assemblée plugin. Vous avez besoin de construire un gros pot. Par exemple, dans sbt :

  • ajouter le fichier $PROJECT_ROOT/project/assembly.sbt avec le code addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.0")
  • pour construire.sbtadded some librarieslibraryDependencies ++= Seq("com.certains.société" %% "quelques-lib" % "1.0.0")`
  • dans sbt console, saisissez "assemblée", et de déployer assemblée jar

Si vous avez besoin de plus d'informations, allez à https://github.com/sbt/sbt-assembly

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X