解决Apache Spark中的依赖性问题

时间:2016-12-29 16:21:31

标签: java scala apache-spark classnotfoundexception nosuchmethoderror

构建和部署Spark应用程序时的常见问题是:

  • java.lang.ClassNotFoundException
  • object x is not a member of package y编译错误。
  • java.lang.NoSuchMethodError

如何解决这些问题?

7 个答案:

答案 0 :(得分:26)

Apache Spark的类路径是动态构建的(以适应每个应用程序的用户代码),这使得它容易受到此类问题的影响。 @user7337271的答案是正确的,但还有一些问题,具体取决于您正在使用的集群管理器(" master")。

首先,Spark应用程序由这些组件组成(每个组件都是一个单独的JVM,因此可能在其类路径中包含不同的类):

  1. 驱动程序您的应用程序创建SparkSession(或SparkContext)并连接到群集管理器以执行实际工作
  2. 群集管理器:用作"入口点"到集群,负责为每个应用程序分配 executors 。 Spark支持几种不同的类型:独立,YARN和Mesos,我们将在下面描述。
  3. 执行者:这些是群集节点上的进程,执行实际工作(运行Spark 任务
  4. 这些关系之间的关系在此图中由Apache Spark的cluster mode overview描述:

    Cluster Mode Overview

    现在 - 哪些类应该驻留在每个组件中?

    可以通过下图解答:

    Class placement overview

    让我们慢慢解析:

    1. Spark Code 是Spark的库。它们应该存在于 ALL 三个组件中,因为它们包含让Spark执行它们之间通信的粘合剂。顺便说一句 - Spark作者做出了一个设计决定,要求在所有组件中包含所有组件的代码(例如,包括只应在驱动程序中的Executor中运行的代码)以简化这一过程 - 所以Spark" fat jar& #34; (版本高达1.6)或"存档" (在2.0中,详细信息如下)包含所有组件的必要代码,并且应该可以在所有组件中使用。

    2. 仅驱动程序代码这是用户代码,不包含应在Executors上使用的任何内容,即在RDD / DataFrame上的任何转换中都不使用的代码/数据集。这不一定必须与分布式用户代码分开,但也可以。

    3. 分布式代码这是用驱动程序代码编译的用户代码,但也必须在执行程序上执行 - 实际转换使用的所有内容都必须包含在此jar中。

    4. 既然我们已经做到了,如何我们是否可以在每个组件中正确加载类,以及它们应遵循哪些规则?

      1. Spark Code :如前所述,您必须在所有组件中使用相同的 Scala Spark 版本。

        1.1在独立模式下,有一个"预先存在的"应用程序(驱动程序)可以连接的Spark安装。这意味着所有驱动程序必须使用在主服务器和执行程序上运行的相同Spark版本

        1.2在 YARN / Mesos 中,每个应用程序可以使用不同的Spark版本,但同一应用程序的所有组件必须使用相同的版本。这意味着如果您使用版本X来编译和打包驱动程序应用程序,则应在启动SparkSession时提供相同的版本(例如,在使用YARN时通过spark.yarn.archivespark.yarn.jars参数)。您提供的jar / archive应该包括所有Spark依赖项(包括传递依赖项),并且在应用程序启动时,集群管理器会将它提供给每个执行程序。

      2. 驱动程序代码:完全取决于 - 驱动程序代码可以作为一堆罐子或者#34;胖罐子运送,只要它包括所有Spark依赖项+所有用户代码

      3. 分布式代码:除了出现在驱动程序中之外,还必须将此代码发送给执行程序(同样,还要附带所有传递依赖项)。这是使用spark.jars参数完成的。

      4. 总结,以下是构建和部署Spark应用程序的建议方法(在本例中为使用YARN):

        • 使用您的分布式代码创建一个库,将其打包为"常规" jar(带有描述其依赖关系的.pom文件)和一个"胖罐" (包括所有传递依赖项)。
        • 创建一个驱动程序应用程序,在分布式代码库和Apache Spark(具有特定版本)上具有编译依赖性
        • 将驱动程序应用程序打包到胖jar中以部署到驱动程序
        • 在启动spark.jars
        • 时,将正确版本的分布式代码作为SparkSession参数的值传递
        • 将包含已下载的Spark二进制文件的lib/文件夹下的所有jar文件的存档文件(例如gzip)的位置作为spark.yarn.archive
        • 的值传递

答案 1 :(得分:18)

构建和部署Spark应用程序时,所有依赖项都需要兼容版本。

  • Scala版本。所有软件包都必须使用相同的主要版本(2.10,2.11,2.12)Scala版本。

    考虑关注(不正确)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"
    )
    

    我们对Scala 2.10使用spark-streaming,而Scala 2.11使用剩余的包。 有效文件可以是

    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"
    )
    

    但最好全局指定版本并使用%%

    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"
    )
    

    同样在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版本所有软件包都必须使用相同的主要Spark版本(1.6,2.0,2.1,...)。

    考虑以下(不正确的)build.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"
    )
    

    我们使用spark-core 1.6,而剩余的组件在Spark 2.0中。 有效文件可以是

    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"
    )
    

    但最好使用变量:

    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
    )
    

    同样在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依赖项中使用的Spark版本必须匹配Spark安装的Spark版本。例如,如果在群集上使用1.6.1,则必须使用1.6.1来构建jar。不一定接受次要版本不匹配。

  • 用于构建jar的Scala版本必须与用于构建部署Spark的Scala版本相匹配。默认情况下(可下载的二进制文件和默认构建版本):

    • Spark 1.x - &gt; Scala 2.10
    • Spark 2.x - &gt; Scala 2.11
  • 如果包含在fat jar中,则应该可以在工作节点上访问其他包。有很多选择,包括:

      {li> --jars spark-submit的参数 - 分发本地jar个文件。 {li> --packages spark-submit的参数 - 从Maven资源库中获取依赖项。

    在群集节点中提交时,您应在jar中添加应用--jars

答案 2 :(得分:3)

除了user7337271已经提供的非常广泛的答案之外,如果问题是由于缺少外部依赖性而导致的,那么您可以构建一个带有依赖关系的jar,例如: maven assembly plugin

在这种情况下,请确保将所有核心火花依赖关系标记为&#34;提供&#34;在您的构建系统中,并且如前所述,确保它们与您的运行时spark版本相关联。

答案 3 :(得分:2)

应用程序的依赖项类应在启动命令的 application-jar 选项中指定。

可在Spark documentation

找到更多详情

取自文件:

  

application-jar:捆绑jar的路径,包括你的应用程序和   所有依赖项。 URL必须在您的内部全局可见   例如,一个hdfs://路径或一个file://路径   存在于所有节点上

答案 4 :(得分:0)

我认为这个问题必须解决一个程序集插件。 您需要建立一个胖子罐。 例如在sbt中:

  • 添加代码为$PROJECT_ROOT/project/assembly.sbt的文件addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.0")
  • 以build.sbt added some libraries libraryDependencies ++ = Seq(“ com.some.company” %%“ some-lib”%“ 1.0.0”)`
  • 在sbt控制台中输入“程序集”,然后部署程序集jar

如果您需要更多信息,请转到https://github.com/sbt/sbt-assembly

答案 5 :(得分:0)

从项目中的spark-2.4.0-bin-hadoop2.7 \ spark-2.4.0-bin-hadoop2.7 \ jars添加所有jar文件。可以从https://spark.apache.org/downloads.html

下载spark-2.4.0-bin-hadoop2.7

答案 6 :(得分:0)

我有以下build.sbt

lazy val root = (project in file(".")).
  settings(
    name := "spark-samples",
    version := "1.0",
    scalaVersion := "2.11.12",
    mainClass in Compile := Some("StreamingExample")        
  )

libraryDependencies ++= Seq(
  "org.apache.spark" %% "spark-core" % "2.4.0",
  "org.apache.spark" %% "spark-streaming" % "2.4.0",
  "org.apache.spark" %% "spark-sql" % "2.4.0",
  "com.couchbase.client" %% "spark-connector" % "2.2.0" 
)

// META-INF discarding
assemblyMergeStrategy in assembly := {
       case PathList("META-INF", xs @ _*) => MergeStrategy.discard
       case x => MergeStrategy.first
   }

我已经使用sbt程序集插件为我的应用创建了一个胖子罐,但是当使用spark-submit运行时,它失败并显示错误:

java.lang.NoClassDefFoundError: rx/Completable$OnSubscribe
    at com.couchbase.spark.connection.CouchbaseConnection.streamClient(CouchbaseConnection.scala:154)

我可以看到该类存在于我的胖子罐中:

jar tf target/scala-2.11/spark-samples-assembly-1.0.jar | grep 'Completable$OnSubscribe'
rx/Completable$OnSubscribe.class

不知道我在这里想念的是什么线索吗?