第一坑:启动方式与local模式迥异
local模式(setMaster("local[2]"))下,SpringBoot集成SparkStreaming的app与其他web应用无差异,直接java -jar xxx.jar即可启动。
Spark on Yan环境下, 必须使用spark-submit等命令,由Spark-Yarn-Client托管应用的jar,否则,应用的jar中缺少向Yarn资源管理器申请资源的模块,无法正常启动。
【对策】
1)应用的pom中引入spark-yarn依赖
|
其中scala.tools.version为scala版本,spark.core.version为spark版本
2)sparkConf设置yarn相关配置
|
3)启动命令
|
各目录的说明:“/opt/xxx/lib/spark”部分为示例机器安装Spark的目录,根据实际情况修改即可。"hdfs://xxx:8020/user/mypath/my-service/lib"部分为示例机器hdfs上存储应用依赖的库文件的目录,根据实际情况修改即可。“/home/mypath/”部分为示例机器上存放spark配置文件的目录,根据实际情况修改即可。
各参数的说明:“spark.yarn.jars”用于指定executor容器运行时依赖的jar的目录,特别是“hdfs://xxx:8020/user/mypath/my-service/lib/*”非常关键,后面将会讲到相关的各种坑。“--properties-file”指定spark自定义配置文件,可绕开许多坑,后面也会讲到。“--driver-java-options”用于指定应用的运行环境参数,绕坑专业户,且听下文分解。“–class”必须指定springboot的main所在class,否则就是坑。
第二坑:logback和log4j冲突
spark-submit内部使用log4j作为日志模块,SpringBoot默认采用logback作为日志模块,默认情况下将出现以下各种异常。
|
【对策】
1)pom中绕开所有logback
|
2)springboot停止尝试加载任何logger
具体办法是在启动命令中增加java参数org.springframework.boot.logging.LoggingSystem,指定为none即可。
|
第三坑:GSON版本冲突
Spark自带的GSON版本可能与SpringBoot依赖的版本冲突,引起如下异常:
|
【对策】
将Spark自带的GSON库重命名
第四坑:Hadoop版本冲突
集群环境的Hadoop版本可能与应用依赖的版本冲突,引起如下异常:
|
【原因】应用依赖了2.6.x版本,而集群环境的是3.0.x
【对策】修改应用pom文件,将hadoop版本改为与集群环境一致
第五坑:运行时抛如下找不到ExecutorLauncher异常
|
【原因】一开始命令行未指定“spark.yarn.jars”参数,导致executor节点缺少依赖库
【对策】命令行指定“spark.yarn.jars”参数,将必要的库目录都加上
第六坑:运行时找不到应用中的类
原因同第五坑
【对策】
1)将应用的jar解压,将lib目录下所有jar上传HDFS目录"hdfs://xxx:8020/user/mypath/my-service/lib"
2)"hdfs://xxx:8020/user/mypath/my-service/lib"追加到命令行“spark.yarn.jars”参数中
3)删除其中可能与集群环境冲突的包
|
4)添加集群环境没有的包
|
5)spark.conf配置文件中使用“spark.executor.extraLibraryPath”参数指定executor节点的库目录
|
第七坑:序列化、反序列化方式冲突
集群环境默认采用kryoserilizer,与应用中默认的JavaSerializer冲突,引起如下异常:
|
【对策】修改spark.conf配置文件,指定采用兼容性更好的JavaSerializer(据说性能差一些)
|
第八坑:lz4压缩组件版本冲突
集群环境和应用中的lz4压缩组件版本可能不同,引起如下异常
|
【对策】修改spark.conf配置文件,指定采用兼容性更好的snappy方式压缩
|
第九坑:低版本Spark有概率触发与netty框架不兼容的bug
低版本Spark(如2.2.2)与netty框架存在兼容性问题,可能出现以下异常。具体场景不明。
java.io.IOException: Failed to send RPC 6940843803625479358 to /xxx:34735: java.lang.AbstractMethodError: org.apache.spark.network.protocol.MessageWithHeader.touch(Ljava/lang/Object;)Lio/netty/util/ReferenceCounted; |
【原因】低版本Spark的org.apache.spark.network.protocol.MessageWithHeader类没有实例化虚函数touch(obj)
【对策】使用Spark2.4.0或更高版本
第十坑:应用增加了对HDFS的依赖后,抛出ClassCastException
java.lang.ClassCastException: cannot assign instance of scala.collection.immutable.List$SerializationProxy to field org.apache.spark.rdd.RDD.org$apache$spark$rdd$RDD$$dependencies_ of type scala.collection.Seq in instance of org.apache.spark.rdd.MapPartitionsRDD |
【原因】SpringBoot默认的打包方式,会将所有依赖的jar打包成一个Fat Jar,运行时很方便,但副作用是其中的Hadoop、Spark相关类库可能与Yarn环境冲突,引起各种奇怪的异常
【对策】对SpringBoot打的包瘦身
1)POM文件修改spring-boot-maven-plugin配置(includes),仅打包项目本身代码
... <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <includes> <include> <groupId>xxx</groupId> <artifactId>yyy-service</artifactId> </include> </includes> </configuration> </plugin> ... |
2)将依赖的jar上传HDFS(剔除Hadoop、Spark相关类库)
参考第六坑。解压spring-boot-maven-plugin配置修改前生成的Fat Jar可得到所有依赖的jar。
3)Yarn环境的Client机器保留一份完整的依赖jar(同样剔除Hadoop、Spark相关类库)
比如放在/home/xxx/lib目录下
4)将只含有项目本身代码的jar上传HDFS,Executor反序列化时需要使用
# xxx-service-1.0.0-SNAPSHOT.jar.original是SpringBoot打包完留在target目录下的只含有项目本身代码的jar # 将其重命名为xxx-api.jar,并放在Driver不会访问的目录下,防止与SpringBoot打的包冲突 mv xxx-service-1.0.0-SNAPSHOT.jar.original otherLib/xxx-api.jar # 每次代码有更新时,删除旧版本 hdfs dfs -rm /xxx/xxx-service/lib/xxx-api.jar # 上传新版本 hdfs dfs -put /xxx/otherLib/xxx-api.jar /xxx/xxx-service/lib/ |
5)spark-submit命令增加-Djava.ext.dirs参数,确保应用能找到所依赖的所有库文件
/bin/spark-submit --conf spark.yarn.jars="local:/xxx/lib/spark/jars/*,local:/xxx/lib/spark/hive/*,hdfs://xxx:8020/xxx/xxx-service/lib/*" --properties-file /xxx/spark.conf --driver-java-options "-Dorg.springframework.boot.logging.LoggingSystem=none -Dspring.profiles.active=test -Dspark.yarn.dist.files=/xxx/yarn-site.xml -Djava.ext.dirs=/xxx/lib:$JAVA_HOME/jre/lib/ext:/xxx/lib/spark/jars:/xxx/lib/spark/hive" --class org.springframework.boot.loader.JarLauncher --master yarn --deploy-mode client xxx-service-1.0.0-SNAPSHOT.jar >> console.log 2>&1 |
第十一坑:代码更新后库文件版本冲突
库文件版本冲突时,会出现如下异常:
java.io.InvalidClassException (xxx.yyy; local class incompatible: stream classdesc serialVersionUID = xxx, local class serialVersionUID = yyy) |
【对策】保持以下几处库文件版本一致。
1)spark-submit提交的xxx-service-1.0.0-SNAPSHOT.jar
2)仅包含项目源码,不含SpringBoot库的jar:hdfs://xxx:8020/xxx/xxx-service/lib/xxx-api.jar
3)java.ext.dirs参数指定的类库目录/xxx/lib中,不要出现上述两个jar
spark.conf示例
|
yarn-site.xml示例
|