Apache Spark 4.0.0 沖突解決指南
1. 問題背景
在嘗試運行一個基于 Apache Spark 4.0.0 的 Java 應用程序。根據 Spark 4.0.0 的發布說明,該版本默認支持 Scala 2.13 和 JDK 17。在初始設置和運行過程中,遇到了以下主要問題:
- 依賴沖突 (POM 問題):Maven 項目的
pom.xml
配置不當,導致依賴解析失敗。 - Java 版本不兼容:盡管
pom.xml
中指定了 JDK 17,但系統默認的 Java 版本 (JDK 21/23) 導致運行時錯誤,包括java.lang.UnsupportedOperationException: getSubject is supported only if a security manager is allowed
。 - Servlet API 兼容性問題:運行應用程序時出現
java.lang.NoClassDefFoundError: jakarta/servlet/SingleThreadModel
錯誤。這是由于 Spark 4.0.0 內部使用了在較新 Servlet API 版本中已棄用或移除的類。
2. 解決方案
為解決上述問題,我們采取了一系列配置和調整措施。
2.1 pom.xml
配置調整
針對依賴和 Java 版本兼容性問題,對 pom.xml
進行了以下關鍵修改:
-
指定 Java 版本: 確保 Maven 項目使用 JDK 17 進行編譯和運行。
<!-- ... existing code ... --> <properties><java.version>17</java.version><maven.compiler.source>${java.version}</maven.compiler.source><maven.compiler.target>${java.version}</maven.compiler.target><spark.version>4.0.0</spark.version><scala.compat.version>2.13</scala.compat.version> </properties> <!-- ... existing code ... -->
-
添加 Spark Core 和 Spark SQL 依賴: 確保 Spark 核心庫和 SQL 模塊正確引入,并設置為
provided
范圍,避免與應用程序的其他依賴沖突。<!-- ... existing code ... --> <dependencies><!-- ... existing dependencies ... --><dependency><groupId>org.apache.spark</groupId><artifactId>spark-core_${scala.compat.version}</artifactId><version>${spark.version}</version><scope>provided</scope><exclusions><exclusion><groupId>jakarta.servlet</groupId><artifactId>jakarta.servlet-api</artifactId></exclusion><exclusion><groupId>org.eclipse.jetty</groupId><artifactId>*</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.apache.spark</groupId><artifactId>spark-sql_${scala.compat.version}</artifactId><version>${spark.version}</version><scope>provided</scope></dependency><!-- ... existing dependencies ... --> </dependencies> <!-- ... existing code ... -->
-
解決 Servlet API 兼容性問題: 為了解決
jakarta.servlet.SingleThreadModel
錯誤(Spark 4.0.0 內部仍在使用),我們顯式排除了spark-core
中的jakarta.servlet-api
和org.eclipse.jetty
依賴,并手動引入了包含該類的較舊版本的 Servlet API (5.0.0)。<!-- ... existing code ... --> <dependency><groupId>jakarta.servlet</groupId><artifactId>jakarta.servlet-api</artifactId><version>5.0.0</version><scope>compile</scope> <!-- Or runtime, depending on specific need --> </dependency> <!-- ... existing code ... -->
注意: 這個問題在 Apache Spark Jira (SPARK-51434) 中有記錄,并計劃在 Spark 4.1.0 中修復。手動引入舊版本 Servlet API 是一個臨時性的解決方案。
-
添加測試依賴: 解決
SparkDemoApplicationTests.java
中的編譯錯誤,引入 Spring Boot 測試依賴。<!-- ... existing code ... --> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><version>3.5.0</version> <!-- Use an appropriate version --><scope>test</scope> </dependency> <!-- ... existing code ... -->
-
Maven Compiler Plugin 配置: 顯式配置 Maven 編譯器插件使用 JDK 17。
<!-- ... existing code ... --> <build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.11.0</version> <!-- Use an appropriate version --><configuration><source>${java.version}</source><target>${java.version}</target></configuration></plugin><!-- ... existing plugins ... --></plugins> </build> <!-- ... existing code ... -->
-
Maven Exec Plugin 配置: 配置
exec-maven-plugin
以便直接運行主類,并添加--add-opens
參數以解決 Java 模塊化系統相關的運行時訪問限制。<!-- ... existing code ... --> <plugin><groupId>org.codehaus.mojo</groupId><artifactId>exec-maven-plugin</artifactId><version>3.1.0</version> <!-- Use an appropriate version --><configuration><mainClass>Spark_RDD.RDDCreateExample</mainClass><executable>java</executable><arguments><argument>--add-opens</argument><argument>java.base/java.nio=ALL-UNNAMED</argument><argument>--add-opens</argument><argument>java.base/java.nio.channels=ALL-UNNAMED</argument><argument>--add-opens</argument><argument>java.base/java.lang=ALL-UNNAMED</argument><argument>--add-opens</argument><argument>java.base/java.util=ALL-UNNAMED</argument><argument>--add-opens</argument><argument>java.base/java.util.concurrent=ALL-UNNAMED</argument><argument>--add-opens</argument><argument>java.base/java.util.concurrent.atomic=ALL-UNNAMED</argument><argument>--add-opens</argument><argument>java.base/jdk.internal.misc=ALL-UNNAMED</argument><argument>--add-opens</argument><argument>java.base/sun.nio.ch=ALL-UNNAMED</argument><!-- Optional: For security manager if needed --><!-- <argument>-Djava.security.manager=allow</argument> --></arguments></configuration> </plugin> <!-- ... existing code ... -->
2.2 Java 環境配置
確保系統環境中的 Java 版本與項目要求一致是至關重要的。
-
設置
JAVA_HOME
環境變量:
通過在 shell 配置文件(如~/.zshrc
或~/.bashrc
)中設置JAVA_HOME
變量,并將其指向 JDK 17 的安裝路徑,可以確保 Maven 和其他工具使用正確的 Java 版本。export JAVA_HOME="/Library/Java/JavaVirtualMachines/openjdk-17.jdk/Contents/Home" export PATH="$JAVA_HOME/bin:$PATH"
修改后,請務必執行
source ~/.zshrc
(或~/.bashrc
) 使更改生效。 -
驗證 Java 和 Maven 版本:
在終端中運行以下命令,驗證 Java 版本和 Maven 使用的 Java 版本是否正確:java -version mvn --version
確保
java -version
顯示的是17.x.x
,并且mvn --version
輸出中 “Java version” 字段也指向 JDK 17。
2.3 Spark UI 禁用 (可選但推薦)
為了規避潛在的 Jetty 或 Servlet API 相關的運行時問題,可以通過 Spark 配置禁用 Spark UI,尤其是在只需要執行批處理任務時。在 RDDCreateExample.java
中添加以下配置:
// ... existing code ...
SparkConf conf = new SparkConf().setAppName("RDD Create").setMaster("local[*]").set("spark.ui.enabled", "false"); // Disable Spark UI
JavaSparkContext sc = new JavaSparkContext(conf);
// ... existing code ...
2.4 Maven Toolchains 嘗試與經驗
在解決 Java 版本問題時,我們曾嘗試使用 Maven Toolchains 來管理不同 JDK 版本。盡管配置了 ~/.m2/toolchains.xml
,但遇到了 Cannot find matching toolchain definitions
和 Toolchain JDK[...] is missing required property: vendor
等錯誤。
經驗總結:雖然 Maven Toolchains 是一個強大的工具,但在某些復雜的 Java 環境(特別是 macOS 上 OpenJDK 的安裝路徑和供應商識別)中,配置可能會比較棘手。在這種情況下,直接通過設置 JAVA_HOME
環境變量來管理 Java 版本,通常是更直接和有效的解決方案,特別是對于單一項目或開發環境。清理 Maven 本地倉庫 (rm -rf ~/.m2/repository/*
) 有助于清除舊的或損壞的依賴,但在 Toolchains 配置問題中,它并未直接解決根本問題。
3. 最終結果
經過上述一系列的配置和調整,應用程序最終成功運行。日志中顯示 Spark 應用程序使用了 Java version 17.0.15
,并且應用程序邏輯正確執行,輸出了預期的結果。
成功運行的關鍵點:
- 在
pom.xml
中嚴格指定了 JDK 17。 - 解決了 Spark 4.0.0 與
jakarta.servlet.SingleThreadModel
之間的兼容性問題,通過手動排除和引入特定版本的 Servlet API。 - 通過設置
JAVA_HOME
確保了整個構建和運行環境都使用了正確的 Java 版本。