在无服务器计算中创建和运行 JAR

重要

Databricks 强烈建议使用声明性自动化捆绑包,而不是按照本页中所述手动生成和部署 JAR。 声明性自动化捆绑包可以轻松地从模板创建项目,该模板具有为无服务器配置的正确 Scala、JDK 和 Databricks Connect 版本,还可以将 JAR 简单部署到 Databricks 工作区。 请参阅 使用声明性自动化捆绑包生成 Scala JAR

重要

无服务器 Scala 和 Java 作业位于 Public Preview中。

Java存档(JAR)包将Java或 Scala 代码打包到单个文件中。 本页介绍如何使用 Spark 代码创建 JAR,并将其部署为 无服务器计算上的 Lakeflow 作业。 可以使用 JAR 任务来部署 JAR 文件。

要求

若要生成 JAR,本地开发环境必须安装以下内容:

  • sbt 1.11.7 或更高版本,用于 Scala JAR 包
  • 用于 Java JAR 的 Maven 3.9.0 或更高版本
  • 无服务器环境匹配的 JDK、Scala 和 Databricks Connect 版本。 请参阅 依赖项版本

依赖项版本

重要

若要在不发生故障的情况下在无服务器计算上运行,JAR Scala 和 JDK 版本必须与运行时 Scala 和 JDK 版本完全匹配。 请参阅 Databricks Connect 版本

此页上的示例使用 无服务器环境版本 4,因此此页面将创建一个 JAR:

  • 针对 Scala 2.13 编译;每个依赖项都使用 _2.13 后缀。
  • 基于 JDK 17 编译,类文件版本为 61。
  • 针对 Databricks Connect 17.3(用于无服务器计算的 Spark API 图面)编译。
  • 仅使用公共 Spark API。 它既不使用 RDD,也不依赖 Spark 内部机制。 请参阅限制
  • 包含 JAR 中的所有依赖项,或将其作为无服务器环境库附加。 请参阅 “管理依赖项”。

局限性

无服务器计算使用 Spark Connect。 您的 JAR 基于一个公开暴露 Spark API 的轻量级客户端库运行,而 Spark 引擎本身则在服务器端运行。 绕过公共 API 的代码无法受益于 Catalyst 优化或 Photon 加速,即使在经典计算上也是如此。 基于 RDD 且依赖于内部的代码通常比等效的数据帧或 SQL 代码慢。

以下内容不可用:

  • RDD API (org.apache.spark.rdd.*) 和 SparkContext / JavaSparkContext. 请改用 SparkSession.builder().getOrCreate() 以及 DataFrame/Dataset 操作。
  • Spark 内部 API(org.apache.spark.catalyst.*、、org.apache.spark.util.*org.apache.spark.sql.util.*org.apache.spark.sql.internal.*)。 导入这些 API 的代码会因 NoClassDefFoundError 而失败。 重构为使用公开的 Spark API。 如果第三方库使用内部版本,请检查它是否发布与 Spark Connect 兼容的版本。
  • 原生库(.so.dll、JNI)。 无服务器计算不允许将原生库写入文件系统。 在启动时解压原生二进制文件的库会因 UnsatisfiedLinkError 而失败。 Init 脚本不是解决方法。 如果有对应的 Java 版本,请使用它。

如果工作负荷需要上述任何一项,请改为在 标准或专用计算 上运行它。

步骤 1:生成 JAR

Scala

  1. 运行以下命令以创建 Scala 项目:

    sbt new scala/scala-seed.g8
    

    出现提示时,输入项目名称,例如 my-spark-app

  2. 接下来,删除种子的存根文件并为源创建目录:

    cd my-spark-app
    rm src/main/scala/example/Hello.scala
    rm src/test/scala/example/HelloSpec.scala
    rm project/Dependencies.scala
    mkdir -p src/main/scala/com/examples
    
  3. 将你的 build.sbt 文件的内容替换为下面的内容:

    name := "my-spark-app"
    
    // Set the dependency versions
    scalaVersion := "2.13.16"
    javacOptions ++= Seq("--release", "17")
    scalacOptions ++= Seq("-release", "17")
    
    libraryDependencies += "com.databricks" %% "databricks-connect" % "17.3.2" % "provided"
    // Your other dependencies go here. Use %% for Scala libraries so sbt picks the _2.13 artifact.
    
    // Fork a new JVM on run so our javaOptions are applied.
    fork := true
    javaOptions += "--add-opens=java.base/java.nio=ALL-UNNAMED"
    
  4. 编辑或创建 project/plugins.sbt 文件,并添加以下行:

    addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.3.1")
    
  5. src/main/scala/com/examples/SparkJar.scala中创建主类:

    package com.examples
    
    import org.apache.spark.sql.SparkSession
    
    object SparkJar {
      def main(args: Array[String]): Unit = {
        val spark = SparkSession.builder().getOrCreate()
    
        // Prints the arguments to the class, which
        // are job parameters when run as a job:
        println(args.mkString(", "))
    
        // Shows using spark:
        println(spark.version)
        println(spark.range(10).limit(3).collect().mkString(" "))
      }
    }
    
  6. 若要生成 JAR 文件,请运行以下命令:

    sbt assembly
    

    编译后的 JAR 会在 target/ 文件夹中创建,名称为 my-spark-app-assembly-0.1.0-SNAPSHOT.jar

Java

  1. 运行以下命令以创建 Maven 项目结构:

    mkdir -p my-spark-app/src/main/java/com/examples
    cd my-spark-app
    
  2. 在项目根目录中创建一个pom.xml文件,并使用以下内容:

    <?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>
    
      <groupId>com.examples</groupId>
      <artifactId>my-spark-app</artifactId>
      <version>1.0-SNAPSHOT</version>
    
      <properties>
        <maven.compiler.release>17</maven.compiler.release>
        <scala.binary.version>2.13</scala.binary.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      </properties>
    
      <dependencies>
        <!-- Included on serverless compute. -->
    
        <dependency>
          <groupId>com.databricks</groupId>
          <artifactId>databricks-connect_${scala.binary.version}</artifactId>
          <version>17.3.2</version>
          <scope>provided</scope>
        </dependency>
      </dependencies>
    
      <build>
        <plugins>
          <!-- Maven Shade Plugin - Creates a fat JAR with all non-provided dependencies. -->
    
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-shade-plugin</artifactId>
            <version>3.6.1</version>
            <executions>
              <execution>
                <phase>package</phase>
                <goals>
                  <goal>shade</goal>
                </goals>
                <configuration>
                  <transformers>
                    <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                      <mainClass>com.examples.SparkJar</mainClass>
                    </transformer>
                  </transformers>
                </configuration>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>
    </project>
    
  3. src/main/java/com/examples/SparkJar.java中创建主类:

    package com.examples;
    
    import org.apache.spark.sql.SparkSession;
    import java.util.stream.Collectors;
    
    public class SparkJar {
      public static void main(String[] args) {
        SparkSession spark = SparkSession.builder().getOrCreate();
    
        // Prints the arguments to the class, which
        // are job parameters when run as a job:
        System.out.println(String.join(", ", args));
    
        // Shows using spark:
        System.out.println(spark.version());
        System.out.println(
          spark.range(10).limit(3).collectAsList().stream()
            .map(Object::toString)
            .collect(Collectors.joining(" "))
        );
      }
    }
    
  4. 若要生成 JAR 文件,请运行以下命令:

    mvn clean package
    

    编译后的 JAR 会在 target/ 文件夹中创建,名称为 my-spark-app-1.0-SNAPSHOT.jar

管理依赖项

若要使您的 JAR 能够在无服务器计算上使用某个库,请执行以下操作:

  • 使用 提供的库:无服务器计算包括 Databricks Connect 和一组特选的公共库。 如果您的版本兼容,请在构建中将其声明为 provided,并且不要将其包含在 JAR 中。
  • 作为环境库附加:如果该库尚未提供,请将其添加到无服务器环境中。 对于不想包含的仅限运行时的库,请使用此功能。
  • 连接到外部数据库:对于 JDBC 源,请使用 JDBC 连接 ,而不是包括驱动程序。 JDBC 连接由 Unity 目录管理。 系统会为你处理凭据、世系和治理。

提供的库

以下库是必需的依赖项,在无服务器计算中默认可用。 在生成中声明它们 provided 。 打包这些库的自有版本会在运行时触发 NoSuchMethodError

注释

下面列出的库版本适用于 无服务器环境版本 4。 有关其他环境版本的已安装库,请参阅 无服务器环境版本说明 参考。

  • com.databricks:databricks-connect_2.13,版本 17.3.2
  • org.scala-lang:scala-library_2.13,版本 2.13.16
  • org.scala-lang:scala-reflect_2.13,版本 2.13.16
  • org.slf4j:slf4j-api,版本 2.0.10
  • org.apache.logging.log4j:log4j-api,版本 2.20.0
  • org.apache.logging.log4j:log4j-core,版本 2.20.0
  • org.apache.httpcomponents:httpclient,版本 4.5.14
  • org.apache.httpcomponents:httpcore,版本 4.4.16
  • com.fasterxml.jackson.core:jackson-databind,版本 2.15.2
  • com.fasterxml.jackson.core:jackson-core,版本 2.15.2
  • com.fasterxml.jackson.core:jackson-annotations,版本 2.15.2
  • com.fasterxml.jackson.datatype:jackson-datatype-jsr310,版本 2.15.2
  • com.google.guava:guava 版本 32.0.1-jre
  • commons-io:commons-io,版本 2.14.0
  • org.json4s:json4s-jackson_2.13,版本 4.0.7
  • org.apache.commons:commons-lang3,版本 3.14.0
  • org.apache.commons:commons-configuration2,版本 2.11.0
  • org.apache.commons:commons-text 版本 1.12.0
  • com.databricks:databricks-sdk-java 版本 0.52.0
  • com.databricks:databricks-dbutils-scala_2.13,版本 0.1.4

步骤 2:创建用于运行 JAR 的作业

  1. 在工作区中,单击工作流图标,然后在边栏中选择作业和管道

  2. 单击创建,然后选择作业

  3. 单击 JAR 磁贴以配置第一个任务。 如果 JAR 磁贴不可用,请单击“ 添加其他任务类型 ”并搜索 JAR

  4. (可选)将默认名称New Job <date-time>替换为您选择的作业名称。

  5. 任务名称中,输入任务的名称,例如 JAR_example

  6. 如有必要,请从“类型”下拉菜单中选择 JAR

  7. 对于 Main 类,请输入 JAR 的包和类。 如果遵循前面的示例,请输入 com.examples.SparkJar

  8. 对于 “计算”,请选择“ 无服务器”。

  9. 配置无服务器环境:

    1. 选择环境,然后单击 铅笔图标。编辑 以对其进行配置。
    2. 环境版本选择 4 或更高版本。
    3. 通过将 JAR 文件拖放到文件选择器,或浏览以从 Unity 目录卷或工作区位置选择它来添加 JAR 文件。
  10. 对于此示例,请输入 作为“参数”。["Hello", "World!"]

  11. 单击“创建任务”。

步骤 3:运行作业并查看作业运行详细信息

单击 立即运行按钮 以运行工作流。 若要查看运行详细信息,请在“已触发的运行”弹出窗口中单击“查看运行”,或者在作业运行视图中单击运行“开始时间”列中的链接。

运行完成后,输出将显示在“ 输出 ”窗格中,包括传递给任务的参数。

故障排除

下表提供了常见异常的故障排除信息。

Exception 原因 修复
NoSuchMethodError scala.*引用类 JAR 针对 Scala 2.12 编译;Serverless 环境运行 Scala 2.13 使用 scalaVersion := "2.13.16" 重新编译。 确保每个 Scala 依赖项都使用 _2.13 跨版本后缀。
NoClassDefFoundError: scala/... Scala 2.12 与 2.13 不匹配 使用 scalaVersion := "2.13.16" 重新编译。 确保每个 Scala 依赖项都使用 _2.13 跨版本后缀。
UnsupportedClassVersionError (版本高于 61 的类文件) 使用 JDK 18 或更高版本编译;Serverless 运行环境使用 JDK 17 使用 <maven.compiler.release>17</maven.compiler.release> (Maven) 或 --release 17 (sbt / javac)
NoClassDefFoundError: org/apache/spark/...用于内部包(catalyst、、utilsql/utilsql/internalapi/javardd 使用了 Spark 内部组件或 RDD API。 这些在 Serverless 环境中不可用。 使用公共 Spark API(DataFrame/Dataset/SQL)。 查看 Serverless 的限制
ClassNotFoundException 用于 JDBC 驱动程序类(例如 oracle.jdbc.OracleDriver JDBC 驱动程序不在 classpath 上 对外部数据库使用 JDBC 连接
ClassNotFoundException用于第三方类(例如 kotlin.jvm.internal.* 该库不在无服务器类路径上。 将其添加到 JAR,或使用 无服务器环境将其作为附加 JAR 提供。
UnsatisfiedLinkError 引用 /tmp/ 下的文件 JAR 中包含的原生库 Serverless 环境不支持本地库。 使用纯Java等效项,或在经典计算上运行。
NoSuchMethodError 来自第三方库(Apache Commons、Guava、Jackson 等) 你所包含的版本与 Serverless 提供的版本 冲突。 使用提供的版本。 在构建中将其标记为 provided,并且不要将其包含在 JAR 中。

后续步骤