【问题标题】:Spark can't find the application class itself (ClassNotFoundException) in spark-submit with SBT assembly JARSpark 在带有 SBT 程序集 JAR 的 spark-submit 中找不到应用程序类本身 (ClassNotFoundException)
【发布时间】:2018-10-20 01:01:28
【问题描述】:

背景

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

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

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

这始终以ClassNotFoundException 失败,声称它找不到我的应用程序的主类。 (下面的详细输出。)

我要强调:

就我的理解而言,我认为这与其他发帖者看到的 ClassNotFoundException 不同,而且我不认为这个问题与这些问题重复。

据我所知,特别是:

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

重现步骤

  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(有适当的替换。)

错误信息

$ ./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

我尝试重命名该类所在的包,包括有一次将其放在顶层包中(没有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 是正确的,我还遗漏了哪些可能导致此类问题的内容?

提前感谢您提供的任何见解或建议。

编辑:分辨率

下面 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)
}

值得注意的是,我之前尝试过一次,但不知何故不起作用。我的怀疑,虽然我无法追溯证明,但我不小心用过时的构建工件测试了这个更改,所以我不小心运行了一个没有这些更改的旧版本。在清理所有内容并使用这些新更改进行重建后,它按预期工作。

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

【问题讨论】:

    标签: scala apache-spark sbt sbt-assembly


    【解决方案1】:

    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"
    

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

    【讨论】:

    • 根据这个建议,我再次查看了assemblyMergeStrategy,最终解决了这个问题。我将详细说明发生了什么,希望如果其他人遇到同样的陷阱,他们将获得更多信息。
    • 我在原帖中评论了更多关于如何修复assemblyMergeStrategy 的细节。再次感谢您的建议。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-05-09
    • 2018-06-04
    • 2015-04-28
    • 2019-05-22
    • 1970-01-01
    • 1970-01-01
    • 2017-04-22
    相关资源
    最近更新 更多