管理 Apache Spark 的 Java 和 Scala 依赖项

Spark 应用通常依赖于第三方 Java 或 Scala 库。以下是在向 Managed Service for Apache Spark 集群提交 Spark 作业时添加这些依赖项的建议方法:

  1. 从本地机器使用 gcloud dataproc jobs submit 命令提交作业时,请使用 --properties spark.jars.packages=[DEPENDENCIES] 标志。

    示例

    gcloud dataproc jobs submit spark \
        --cluster=my-cluster \
        --region=region \
        --properties=spark.jars.packages='com.google.cloud:google-cloud-translate:1.35.0,org.apache.bahir:spark-streaming-pubsub_2.11:2.2.0'
    

  2. 直接在集群上提交作业时, 请使用带有 --packages=[DEPENDENCIES] 参数的 spark-submit 命令。

    示例

    spark-submit --packages='com.google.cloud:google-cloud-translate:1.35.0,org.apache.bahir:spark-streaming-pubsub_2.11:2.2.0'
    

  3. 添加自定义 JAR 文件或软件包时:

    • 作业专用依赖项提交作业时,使用 --jars 标志、 Google Cloud 控制台 Jar 文件 字段或 API jarFileUris 字段指定 JAR 的 Cloud Storage 路径。
    • 集群范围的依赖项: 在创建集群期间使用 spark:spark.jars 集群 属性 (--properties="spark:spark.jars=gs://<var>your-bucket</var>/<var>your-jarfile</var>.jar")。

避免依赖项冲突

如果 Spark 应用依赖项与 Hadoop 的依赖项冲突,则上述方法可能会失败。导致这种冲突的原因如下:由于 Hadoop 将其 依赖项注入应用的 类路径,因此其依赖项的优先级高于应用的 依赖项。发生冲突时,可能会生成 NoSuchMethodError 或其他错误。

示例
Guava 是 Google 的 Java 核心库,可供多种库和 框架(包括 Hadoop)使用。如果作业或其依赖项需要使用的 Guava 版本比 Hadoop 使用的版本更高,则可能出现依赖项冲突。

Hadoop v3.0 解决了

这个问题,但依赖早期 Hadoop 版本的应用需要通过以下两个环节的解决方法来避免可能的依赖项冲突。

  1. 创建一个单独的 JAR,其中包含应用的软件包及其所有依赖项。
  2. 重新定位超级 JAR 中冲突的依赖项软件包,以防止其路径名与 Hadoop 的依赖项软件包冲突。 可在打包过程中使用插件(请参阅下文)自动执行此重定位(也称为“添加阴影”),而无需修改您的代码。

用 Maven 创建一个超级阴影 JAR

Maven 是构建 Java 应用程序的包管理工具。Maven scala 插件可用于构建用 Scala(即 Spark 应用使用的语言)编写的应用。The Maven shade

插件可用于创建阴影 JAR。

以下是 pom.xml 配置文件示例,用于为位于 com.google.common 软件包中的 Guava 库添加阴影。此配置 指示 Maven 将 com.google.common 软件包重命名为 repackaged.com.google.common,并更新 原始软件包中对类的所有引用。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <properties>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <groupId><!-- YOUR_GROUP_ID --></groupId>
  <artifactId><!-- YOUR_ARTIFACT_ID --></artifactId>
  <version><!-- YOUR_PACKAGE_VERSION --></version>

  <dependencies>

    <dependency>
      <groupId>org.apache.spark</groupId>
      <artifactId>spark-sql_2.11</artifactId>
      <version><!-- YOUR_SPARK_VERSION --></version>
      <scope>provided</scope>
    </dependency>

    <!-- YOUR_DEPENDENCIES -->

  </dependencies>

  <build>
    <plugins>

      <plugin>
        <groupId>net.alchim31.maven</groupId>
        <artifactId>scala-maven-plugin</artifactId>
        <executions>
          <execution>
            <goals>
              <goal>compile</goal>
              <goal>testCompile</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <scalaVersion><!-- YOUR_SCALA_VERSION --></scalaVersion>
        </configuration>
      </plugin>

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <transformers>
                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                  <mainClass><!-- YOUR_APPLICATION_MAIN_CLASS --></mainClass>
                </transformer>
                <!-- This is needed if you have dependencies that use Service Loader. Most Google Cloud client libraries do. -->
                <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
              </transformers>
              <filters>
                <filter>
                  <artifact>*:*</artifact>
                  <excludes>
                    <exclude>META-INF/maven/**</exclude>
                    <exclude>META-INF/*.SF</exclude>
                    <exclude>META-INF/*.DSA</exclude>
                    <exclude>META-INF/*.RSA</exclude>
                  </excludes>
                </filter>
              </filters>
              <relocations>
                <relocation>
                  <pattern>com</pattern>
                  <shadedPattern>repackaged.com.google.common</shadedPattern>
                  <includes>
                    <include>com.google.common.**</include>
                  </includes>
                </relocation>
              </relocations>
            </configuration>
          </execution>
        </executions>
      </plugin>

    </plugins>
  </build>

</project>

要运行该构建,请执行以下操作:

mvn package

关于 pom.xml 的备注

  • ManifestResourceTransformer 会处理 Uber JAR 清单文件 (MANIFEST.MF) 中的属性。此 清单还可以指定应用的入口点。
  • Spark 的 范围provided,因为 Spark 安装在 Managed Service for Apache Spark 上。
  • 指定安装在 Managed Service for Apache Spark 集群上的 Spark 版本(请参阅 Managed Service for Apache Spark 版本列表 )。如果您的应用需要的 Spark 版本与 Managed Service for Apache Spark 集群中安装的 版本不同,您可以 编写初始化 操作, 或构建一个自定义映像, 安装应用使用的 Spark 版本。
  • <filters> 条目会从依赖项的 META-INF 目录中排除签名文件。如果没有此条目,则可能会发生 java.lang.SecurityException: Invalid signature file digest for Manifest main attributes 运行时 异常,因为签名文件在超级 JAR 环境中无效。
  • 您可能需要为多个库添加阴影。为此,请添加多个路径。 以下示例演示了如何为 Guava 和 Protobuf 库添加阴影。
    <relocation>
      <pattern>com</pattern>
      <shadedPattern>repackaged.com</shadedPattern>
      <includes>
        <include>com.google.protobuf.**</include>
        <include>com.google.common.**</include>
      </includes>
    </relocation>

用 SBT 创建一个超级阴影 JAR

SBT 是构建 Scala 应用的工具。如需使用 SBT 创建阴影 JAR,请首先向构建定义中添加 sbt-assembly

插件,然后在 project/ 目录下创建名为 assembly.sbt 的文件:

├── src/
└── build.sbt
└── project/
    └── assembly.sbt

然后在 assembly.sbt 中添加以下行:

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

以下是 build.sbt 配置文件示例,用于为位于 com.google.common package 软件包中的 Guava 库添加阴影。

lazy val commonSettings = Seq(
 organization := "YOUR_GROUP_ID",
 name := "YOUR_ARTIFACT_ID",
 version := "YOUR_PACKAGE_VERSION",
 scalaVersion := "YOUR_SCALA_VERSION",
)

lazy val shaded = (project in file("."))
 .settings(commonSettings)

mainClass in (Compile, packageBin) := Some("YOUR_APPLICATION_MAIN_CLASS")

libraryDependencies ++= Seq(
 "org.apache.spark" % "spark-sql_2.11" % "YOUR_SPARK_VERSION" % "provided",
 // YOUR_DEPENDENCIES
)

assemblyShadeRules in assembly := Seq(
  ShadeRule.rename("com.google.common.**" -> "repackaged.com.google.common.@1").inAll
)

要运行该构建,请执行以下操作:

sbt assembly

关于 build.sbt 的备注

  • 上述示例中的阴影规则可能无法解决所有依赖项冲突,因为 SBT 使用严格的冲突解决策略。因此,您可能需要提供更精细的规则,这些规则使用 MergeStrategy.firstlastconcatfilterDistinctLinesrenamediscard 策略明确合并特定类型的冲突文件。如需了解详情,请参阅 sbt-assembly's 合并 策略
  • 您可能需要为多个库添加阴影。为此,请添加多个路径。 以下示例演示了如何为 Guava 和 Protobuf 库添加阴影。
    assemblyShadeRules in assembly := Seq(
      ShadeRule.rename("com.google.common.**" -> "repackaged.com.google.common.@1").inAll,
      ShadeRule.rename("com.google.protobuf.**" -> "repackaged.com.google.protobuf.@1").inAll
    )

向 Managed Service for Apache Spark 提交超级 JAR

在创建包含 Spark 应用 及其依赖项的超级阴影 JAR 之后,将 JAR 上传到 Cloud Storage,然后向 Managed Service for Apache Spark 提交作业 (使用--jars 标志在 Google Cloud CLI CLI 中,控制台中的Jar 文件 字段, Google Cloud API jarFileUris 字段,或将阴影 JAR 指定为主要 应用 JAR)。

后续步骤