Apache|Apache Zeppelin 迁移 - Jar 包冲突解决与思考
最近整个公司大数据集群迁移(cdh -> ambri hdp),随之 Zeppelin 也需要迁移,由于各个组件版本有变化,且 Zeppelin 源码是有过改动的,迁移起来很麻烦。经过一周的折腾,终于把 Zeppelin 从 cdh 环境迁移至 hdp 环境。同时,在解决问题期间,对 Java 类加载,jar 冲突问题有了更进一步的认识。
1 迁移前后环境
1.1当前环境
- zeppelin: 0.7.2
- hadoop:cdh 2.6.0
- spark: 2.0
- hdp: 2.6.0.3-8
- hadoop: 2.7.3.2.6.0.3-8 ( hortonworkds compiled )
- spark: 2.1
mvn clean package -Pbuild-distr -Pspark-2.1 -Phadoop-version2.7.3 -Pscala-2.11 -DskipTests -Dcheckstyle.skip=true
经过配置参数修改,迁移过去 hive 没问题,spark 运行报错,经过一周的折腾终于解决,主要是版本依赖冲突问题。
2 冲突解决 2.1 hadoop 公共组件冲突解决:
- 现象:执行报相关的任务报 ClassNotFoundExecption NoSuchMethodError 等错误。
- 冲突的包:hadoop-common.jar 和 hadoop-auth.jar
- 目录:$zeppelin_home/lib
- 解决方案:从 HDP 相关的目录拷贝并替换对应的包,版本升级:2.6.0 -> 2.7.3
1. java.lang.NoSuchMethodError:com.facebook.fb303.FacebookService$Client.sendBaseOneway
2. (Ljava/lang/String;
Lorg/apache/thrift/TBas
3. at com.facebook.fb303.FacebookService$Client.send_shutdown(FacebookService.java:436)
4. at com.facebook.fb303.FacebookService$Client.shutdown(FacebookService.java:430)
5. at org.apache.hadoop.hive.metastore.HiveMetaStoreClient.close(HiveMetaStoreClient.java:558)
6. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
7. at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
8. at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
9. at java.lang.reflect.Method.invoke(Method.java:498)
10. at org.apache.hadoop.hive.metastore.RetryingMetaStoreClient.invoke(RetryingMetaStoreClient.java:178)
11. at com.sun.proxy.$Proxy22.close(Unknown Source)
连 hive metastore 后调用
close()
方法, FacebookService(from: libfb303.jar) 最终调用 TClient.sendBaseOneway
(to: libthrift.jar)查看 libthrift 源码, 0.9.3 版本有
sendBaseOneway
, 而 0.9.2 版本没有。结论: libfb303.jar 的 facebookservice 调用了 libthrift 0.9.3 版本的中的方法,而 JVM 加载了 0.9.2 版本, 导致 NoSuchMethodError。
查看 zeppelin 源码,
zeppelin-spark\*.jar
,zeppelin-spark-dependencices\*.jar
都 shaded 了 libthrift 这个 jar 包。打包信息如下:
INFO] Building jar: D:\flyao\Idea\zeppelin-0.7.2\spark\target\zeppelin-spark_2.10-0.7.2.jar
[INFO]
[INFO] --- maven-shade-plugin:2.3:shade (default) @ zeppelin-spark_2.10 ---
[INFO] Including org.apache.zeppelin:zeppelin-display_2.11:jar:0.7.2 in the shaded jar.
[INFO] Including org.apache.zeppelin:zeppelin-interpreter:jar:0.7.2 in the shaded jar.
[INFO] Including org.apache.thrift:libthrift:jar:0.9.3 in the shaded jar.
jar 所在目录:
-
$zeppelin_home/lib/interpreter
: zeppelin-spark*.jar -
$zeppelin_home/interpreter/spark/dep
: zeppelin-spark-dependencices*.jar
x
解决方案:
升级 libthrift jar 版本:0.9.2 -> 0.9.3
1.将 pom 里 libthrift 的版本升至 0.9.3
2.重新打包:mvn clean package -Pspark-2.1 -Phadoop-2.7 -Pscala-2.11 -DskipTests
3 Java Jar 包冲突思考 如果深入理解 Java 类加载机制,jar包冲突相关原因,对定位解决这类问题有很大的帮助 。
一般来说遇到:
ClassNotFoundExpection
, NoClassDefFoundError
和 NoSuchMethodError
,有可能是包冲突造成的。先来解释下这个三个问题的区别 [1]:
-
ClassNotFoundExpection
:是一个可以恢复的 expection;动态加载 class 的时候(Class.forName("") 或 classloader loadClass时),classpath 找不到对应的文件。 -
NoClassDefFoundError
:JVM runtime 抛出的 ERROR:compile time 可以找到的 class,在 runtime 间(通过 new 或者方法调用 )无法加载。当然出现问题的情况也有多种:class 文件确实不在,被修改,这个类依赖的类出现加载问题,或者静态初始化抛出异常等。 -
NoSuchMethodError
:同样是 JVM runtime ERROR,编译期间可以找到的方法,runtime 期间找不到了。这次典型的包冲突导致NoSuchMethodError
。
- 同一个Jar包出现了多个不同版本:相同的 jar 包(名 group artifict),不同的版本:例如开源 jar 包更新版本。libthrift-0.9.2.jar libthrift-0.9.3.jar
- 同一个类出现在多个不同 Jar 包中:不同的 jar 包(名group artifict ),同一个类(同样的类限定词)出现在不同的包中,例如:commons-lang 和 commons-lang3
ClassNotFoundExpection
, NoClassDefFoundError
和 NoSuchMethodError
等异常。3.1 jar 包冲突 - maven 仲裁机制 因为 maven 的传递依赖机制,maven 引入依赖类似于图的遍历,从子往父溯源,引入所有相关依赖。这样为开发节省了效率,但同时可能引入不同版本的 jar 包,导致在运行时出现包冲突。存在多个依赖,maven 具体选择引入哪个依赖,规范来源于仲裁机制,仲裁机制如下:
- 首先依据
中声明的版本,此时下面的两个原则都无效了 - 依据依赖树中路径最短的版本
- 路径相同,则按照“第一声明优先”的原则进行仲裁,即选择POM中最先声明的版本
-
是解决冲突的常用手段 -
排除相关冲突依赖
org.apache.avro
avro
${avro.version}
org.apache.avro
avro-ipc
${avro.version}
io.netty
netty
3.2 jar 包冲突 - jar 包加载顺序 还有一种冲突是同样的类,出现在不同的包里面,例如 A 和 B 包都有类 C,JVM 在加载 C 的时候到底是选择 A 还是 B。这个选择取决于:
- Jar 包所处的加载路径,或者换个说法就是加载该 Jar 包的类加载器在 JVM 类加载器树结构中所处层级。例如 bootstrap classloader 还是 app classloader 路径下。
- 文件系统的文件加载顺序。这个因素很容易被忽略,对于 linux 文件系统来说, 可能是按照文件的 inode 排序决定。所以测试与生产环境是否一致很重要。
ClassNotFoundExpection
, NoClassDefFoundError
和 NoSuchMethodError
,有可能是包冲突造成的。- 定位 jar 包: 根据日志查询对应的 class 所在 jar 包;可以直接在 IDEA 双击 shift 搜索,并定位到 jar 包,或者自己写段脚本遍历 jar 包搜索。
- 查看引入方: 通过
mvn dependency:tree -Dverbose -Dincludes=
查看是哪些地方引入。另外可以通过 IDEA Maven helper 插件来查看依赖冲突。: - 解决冲突:可用
排除不需要的 Jar 包版本或者在依赖管理 中申明版本。
[2] classnotfoundexception-vs-noclassdeffounderror
[3] 重新看待Jar包冲突问题及解决方案
推荐阅读
- Apache多路复用模块(MPMs)介绍
- mac|mac php5.6+mongdb+Apache环境配置
- centos7|centos7 redis安装/集群部署/slots迁移
- Keras|将Pytorch模型迁移到android端(android studio)【未实现】
- 开源生态|GPL、MIT、Apache...开发者如何选择开源协议(一文讲清根本区别)
- Apache|Apache Zookeeper总结
- Apache|Apache BookKeeper中数据目录分析
- 阿里云配置|阿里云配置 apache 升级https 部署
- 32位以及64位栈迁移的具体分析与学习
- IDEA|IDEA 中使用MAVEN Install 项目的时候 报 org.apache.maven.plugins:maven-surefire-plugin:2.18.1:test failed