Spark无法使用SBT程序集JAR

时间:2018-05-09 20:16:09

标签: scala apache-spark sbt sbt-assembly

背景

我正在尝试使用Scala开始使用Spark。

最初,我试图编写一个流式Kinesis消费者,跟随this official example。 虽然,此时我已经减少了我的错误案例以删除任何与Kinesis相关的内容,除了包依赖项,并且错误保持不变。

我使用SBT生成项目的程序集JAR。然后,我尝试使用spark-submit在本地运行它。 (详细步骤如下。)

这始终是ClassNotFoundException失败,声称无法找到我的应用程序的主类。 (详细输出如下。)

我应该强调:

就我的理解而言,我不相信这与其他海报看到的ClassNotFoundException相同,我不相信这个问题是这些问题的副本。

特别是,据我所知:

  • 有问题的课程是我的主要应用课程本身。
  • 我引用了正确的完全限定类名。
  • 我通过SBT生成程序集JAR,将依赖项包含在输出JAR中。

Repro步骤

  1. 初始化空的Scala SBT项目。 (我在IntelliJ中使用了默认模板,但我认为它不重要。)
  2. 添加项目代码。 (包括在下面。)
  3. sbt assembly
  4. ./bin/spark-submit --class "sparkpoc.KinesisExample" --master local[4] ~/git/ming-spark-poc/target/scala-2.11/ming-spark-poc-assembly-0.1.jar(有适当的替换。)
  5. 错误消息

    $ ./bin/spark-submit --class "sparkpoc.KinesisExample" --master local[4] ~/git/ming-spark-poc/target/scala-2.11/ming-spark-poc-assembly-0.1.jar 
    WARNING: An illegal reflective access operation has occurred
    WARNING: Illegal reflective access by org.apache.hadoop.security.authentication.util.KerberosUtil (file:/Users/ming/misc/spark-2.3.0-bin-hadoop2.7/jars/hadoop-auth-2.7.3.jar) to method sun.security.krb5.Config.getInstance()
    WARNING: Please consider reporting this to the maintainers of org.apache.hadoop.security.authentication.util.KerberosUtil
    WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
    WARNING: All illegal access operations will be denied in a future release
    2018-05-09 15:39:01 WARN  NativeCodeLoader:62 - Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
    java.lang.ClassNotFoundException: sparkpoc.KinesisExample
        at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:466)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:566)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:499)
        at java.base/java.lang.Class.forName0(Native Method)
        at java.base/java.lang.Class.forName(Class.java:374)
        at org.apache.spark.util.Utils$.classForName(Utils.scala:235)
        at org.apache.spark.deploy.SparkSubmit$.org$apache$spark$deploy$SparkSubmit$$runMain(SparkSubmit.scala:836)
        at org.apache.spark.deploy.SparkSubmit$.doRunMain$1(SparkSubmit.scala:197)
        at org.apache.spark.deploy.SparkSubmit$.submit(SparkSubmit.scala:227)
        at org.apache.spark.deploy.SparkSubmit$.main(SparkSubmit.scala:136)
        at org.apache.spark.deploy.SparkSubmit.main(SparkSubmit.scala)
    2018-05-09 15:39:01 INFO  ShutdownHookManager:54 - Shutdown hook called
    2018-05-09 15:39:01 INFO  ShutdownHookManager:54 - Deleting directory /private/var/folders/py/jrf50pwj1xdd4grjvlg07g580000gp/T/spark-c5f3bade-fbfe-4516-900e-99fee1b47366
    

    我的代码

    build.sbt

    name := "ming-spark-poc"
    
    version := "0.1"
    
    scalaVersion := "2.11.8"
    
    val sparkVersion = "2.3.0"
    
    libraryDependencies ++= Seq(
      "org.apache.spark" %% "spark-sql" % sparkVersion % "provided",
      "org.apache.spark" %% "spark-streaming" % sparkVersion % "provided",
      "org.apache.spark" %% "spark-streaming-kinesis-asl" % sparkVersion
    )
    
    assemblyOption in assembly := (assemblyOption in assembly).value
      .copy(includeScala = false)
    
    assemblyMergeStrategy in assembly := {
      case PathList("META-INF", "MANIFEST.MF") => MergeStrategy.discard
      case _ => MergeStrategy.first
    }
    

    我知道在没有默认情况下设置assemblyMergeStrategy是生产代码中的不良做法。这只是一个快速破解项目的构建,据我所知,它与我当前的错误无关。

    assembly.sbt

    addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.6")
    

    KinesisExample.scala

    最初,这是一个Kinesis消费者。 它已被缩减为一个什么都不做的占位符应用程序。 错误没有改变。

    package sparkpoc
    
    import scala.collection.mutable
    import org.apache.spark.SparkConf
    import org.apache.spark.rdd.RDD
    import org.apache.spark.streaming.{Seconds, StreamingContext}
    
    object KinesisExample {
    
      def main(args: Array[String]): Unit = {
        val batchInterval = Seconds(5)
    
        val sparkConf = new SparkConf().setAppName("SparcPocKinesisExample")
        val streamingContext = new StreamingContext(sparkConf, batchInterval)
    
        streamingContext.start()
        streamingContext.awaitTermination()
      }
    
    }
    

    到目前为止我尝试了什么

    我可以运行预先打包的JAR中的官方示例,看似没有问题。

    $ ./bin/run-example SparkPi 10
    WARNING: An illegal reflective access operation has occurred
    WARNING: Illegal reflective access by org.apache.hadoop.security.authentication.util.KerberosUtil (file:/Users/ming/misc/spark-2.3.0-bin-hadoop2.7/jars/hadoop-auth-2.7.3.jar) to method sun.security.krb5.Config.getInstance()
    WARNING: Please consider reporting this to the maintainers of org.apache.hadoop.security.authentication.util.KerberosUtil
    WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
    WARNING: All illegal access operations will be denied in a future release
    2018-05-09 16:14:07 WARN  NativeCodeLoader:62 - Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
    2018-05-09 16:14:08 INFO  SparkContext:54 - Running Spark version 2.3.0
    2018-05-09 16:14:08 INFO  SparkContext:54 - Submitted application: Spark Pi
    <SNIPPED>
    

    据我所知,生成的JAR文件包含预期的类文件。我以两种不同的方式独立验证了这一点。

    我用jar -tf检查了JAR的内容。据我所知,它包含预期位置的预期类文件。

    $ jar -tf ./target/scala-2.11/ming-spark-poc-assembly-0.1.jar | grep KinesisExample
    org/apache/spark/examples/streaming/KinesisExampleUtils$$anonfun$getRegionNameByEndpoint$1.class
    org/apache/spark/examples/streaming/KinesisExampleUtils$$anonfun$getRegionNameByEndpoint$2.class
    org/apache/spark/examples/streaming/KinesisExampleUtils$$anonfun$getRegionNameByEndpoint$3.class
    org/apache/spark/examples/streaming/KinesisExampleUtils$.class
    org/apache/spark/examples/streaming/KinesisExampleUtils.class
    sparkpoc/KinesisExample$.class
    sparkpoc/KinesisExample.class
    

    我用unzip提取了JAR的内容并手动检查了它们。据我所知,它包含预期位置的预期类文件。

    虽然我不希望此项目中的任何内容与当前工作目录相关,但我使用Spark安装根作为当前工作目录重复相同的步骤,结果没有变化。

    我尝试直接运行生成的JAR。虽然我不希望这样做能够正常运行Spark应用程序,但我认为它可以提供对类解决方案正在发生的事情的深入了解。它失败如下。

    $ java -jar ./target/scala-2.11/ming-spark-poc-assembly-0.1.jar "sparkpoc.KinesisExample"
    Error: Could not find or load main class sparkpoc.KinesisExample
    Caused by: java.lang.ClassNotFoundException: sparkpoc.KinesisExample
    

    我尝试重命名该类所在的包,包括将其放在toplevel包中(没有package声明)。每次使用适当的完全限定类名称调用spark-submit时,我仍然会遇到相同的错误。

    如果assemblyMergeStrategy hack间接破坏了某些内容,我尝试将其替换为如下的显式列表。

    assemblyMergeStrategy in assembly := {
      case PathList("javax", "inject", _*) => MergeStrategy.last
      case PathList("org", "apache", _*) => MergeStrategy.last
      case PathList("org", "aopalliance", _*) => MergeStrategy.last
      case PathList("mime.types") => MergeStrategy.last
      case x =>
        val oldStrategy = (assemblyMergeStrategy in assembly).value
        oldStrategy(x)
    }
    

    我仍然遇到同样的错误。编辑:这实际上按预期工作。我有一个陈旧的构建工件的单独问题。见下文。

    问题

    1. 我目前的SBT设置有什么不正确或不正确吗? (除了assemblyMergeStrategy中的黑客。)
    2. 我如何验证生成的JAR是否正确?
    3. 假设生成的JAR是正确的,我还缺少什么可能导致这样的问题?
    4. 提前感谢您提供的任何见解或建议。

      编辑:分辨率

      以下Ahmad Ragab的答案是正确的。

      我的assemblyMergeStrategy确实出现了问题,导致输出JAR格式不正确。在我理解的有限范围内,我相信我在assemblyMergeStrategy中使用过于激进的通配符破坏了一些重要的元数据。有效的版本如下。

      assemblyMergeStrategy in assembly := {
        case PathList("javax", "inject", _*) => MergeStrategy.last
        case PathList("org", "apache", _*) => MergeStrategy.last
        case PathList("org", "aopalliance", _*) => MergeStrategy.last
        case PathList("mime.types") => MergeStrategy.last
        case x =>
          val oldStrategy = (assemblyMergeStrategy in assembly).value
          oldStrategy(x)
      }
      

      值得注意的是,之前我曾尝试过这种方法,但它在某种程度上并没有起作用。我怀疑,虽然我不能追溯地证明它,但我是用一个过时的构建工件意外地测试了这个变化,所以我不小心运行了没有这些更改的旧版本。清理完所有内容并使用这些新更改进行重建后,它按预期工作。

      (请注意,由于没有已定义的输出,应用程序仍会在启动时崩溃,但当然,该故障是预期的,我们已删除了意外的故障。)

1 个答案:

答案 0 :(得分:2)

assemblyMergeStrategy试试看起来有点奇怪:

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

其次,您可能在汇编选项中需要显式设置您的主类,以便创建正确的清单,尽管公平,我无法证明这一点。在过去,我已经取得了一些成功

mainClass in assembly := Some("sparkpoc.KinesisExample")

最后,您可以确认正确创建jar的一种方法是执行此操作:

java -classpath ming-spark-poc-assembly-0.1.jar "sparkpoc.KinesisExample"

希望其中一些能引导您朝着正确的方向前进。