使用spark-submit运行Spring Boot应用时遭遇ClassNotFoundException问题排查
问题原因分析与解决方案
这个ClassNotFoundException本质上是Spark在YARN Cluster模式下无法找到Spring Boot的核心类,主要有两个关键原因:
1. Spring Boot可执行Jar的结构与Spark ClassLoader不兼容
你用Spring Boot默认插件打包出来的Jar是可执行Jar,它的依赖包都放在BOOT-INF/lib目录下,而Spark的ClassLoader在Cluster模式下并不会自动扫描这个路径下的类,导致Spring Boot的核心类(比如SpringApplication)无法被加载。
2. Cluster模式下的依赖分发问题
在yarn-cluster模式中,Driver进程是在YARN集群的某个节点上启动的,而不是本地机器。如果你的Jar是"瘦Jar"(只包含自己的代码,依赖在本地),集群节点上没有这些依赖,自然会找不到类。即使你本地有依赖,Spark也不会自动把本地依赖上传到集群。
解决办法
方法一:用Maven Shade插件打包成标准Fat Jar
修改你的pom.xml,添加maven-shade-plugin,它会把所有依赖(包括Spring Boot的核心库)合并到一个Jar中,并且生成Spark能识别的Classpath结构:
<build> <plugins> <!-- 跳过Spring Boot默认的可执行Jar生成,改用Shade插件打包 --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <skip>true</skip> </configuration> </plugin> <!-- 添加Shade插件 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.2.4</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <!-- 指定主类入口 --> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>sample.dummy</mainClass> </transformer> <!-- 处理Spring Boot的资源文件冲突 --> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.handlers</resource> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.schemas</resource> </transformer> </transformers> </configuration> </execution> </executions> </plugin> </plugins> </build>
执行mvn clean package后,会生成一个包含所有依赖的Fat Jar,用这个Jar重新执行spark-submit命令即可。
方法二:在spark-submit中显式指定依赖Jar(不推荐)
如果你不想修改打包方式,可以把Spring Boot相关的依赖Jar收集到集群可访问的路径(比如HDFS),然后在spark-submit中用--jars参数指定这些Jar:
./spark-submit \ --class sample.dummy \ --master yarn-cluster \ --driver-memory 5G \ --deploy-mode cluster \ --executor-memory 7G \ --executor-cores 1 \ --num-executors 1 \ --conf spark.yarn.executor.memoryOverhead=1024 \ --jars hdfs:///path/to/spring-boot-starter.jar,hdfs:///path/to/other-dependencies.jar \ /hadoop/app/abc/bootup/sample-0.0.1-SNAPSHOT.jar \ --runner=SparkRunner
这种方式需要手动管理所有依赖,容易遗漏,所以更推荐第一种方法。
内容的提问来源于stack exchange,提问作者Pratik Joshi




