最近整個公司大數(shù)據(jù)集群遷移(cdh -> ambri hdp),隨之 Zeppelin 也需要遷移满哪,由于各個組件版本有變化亦渗,且 Zeppelin 源碼是有過改動的,遷移起來很麻煩任柜。經(jīng)過一周的折騰,終于把 Zeppelin 從 cdh 環(huán)境遷移至 hdp 環(huán)境寒波。同時乘盼,在解決問題期間升熊,對 Java 類加載俄烁,jar 沖突問題有了更進一步的認識。
1 遷移前后環(huán)境
1.1當前環(huán)境
- zeppelin: 0.7.2
- hadoop:cdh 2.6.0
- spark: 2.0
1.2 遷移環(huán)境
- hdp: 2.6.0.3-8
- hadoop: 2.7.3.2.6.0.3-8 ( hortonworkds compiled )
- spark: 2.1
版本有所變化级野,當然得重新編譯 Zeppelin 页屠,指定下 spark hadoop 大版本,重新編譯:
mvn clean package -Pbuild-distr -Pspark-2.1 -Phadoop-version2.7.3 -Pscala-2.11 -DskipTests -Dcheckstyle.skip=true
經(jīng)過配置參數(shù)修改蓖柔,遷移過去 hive 沒問題辰企,spark 運行報錯,經(jīng)過一周的折騰終于解決况鸣,主要是版本依賴沖突問題牢贸。
2 沖突解決
2.1 hadoop 公共組件沖突解決:
- 現(xiàn)象:執(zhí)行報相關(guān)的任務(wù)報 ClassNotFoundExecption NoSuchMethodError 等錯誤。
- 沖突的包:hadoop-common.jar 和 hadoop-auth.jar
- 目錄:$zeppelin_home/lib
- 解決方案:從 HDP 相關(guān)的目錄拷貝并替換對應(yīng)的包镐捧,版本升級:2.6.0 -> 2.7.3
2.2 libthrift 與 libfb303 jar 沖突解決:
現(xiàn)象:跑 spark sql 報 NoSuchMethodError 錯誤如下:
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 后調(diào)用 close()
方法, FacebookService(from: libfb303.jar) 最終調(diào)用 TClient.sendBaseOneway
(to: libthrift.jar)
查看 libthrift 源碼, 0.9.3 版本有 sendBaseOneway
潜索, 而 0.9.2 版本沒有臭增。
結(jié)論: libfb303.jar 的 facebookservice 調(diào)用了 libthrift 0.9.3 版本的中的方法,而 JVM 加載了 0.9.2 版本竹习, 導(dǎo)致 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
這里也有一個取巧的方法,將 jar 包解壓刪除 thrift 并重新打包泌辫。通過 linux 下 jar 命令即可完成随夸。
3 Java Jar 包沖突思考
如果深入理解 Java 類加載機制,jar包沖突相關(guān)原因震放,對定位解決這類問題有很大的幫助 逃魄。
一般來說遇到:ClassNotFoundExpection
, NoClassDefFoundError
和 NoSuchMethodError
,有可能是包沖突造成的澜搅。
先來解釋下這個三個問題的區(qū)別 [1]:
-
ClassNotFoundExpection
:是一個可以恢復(fù)的 expection伍俘;動態(tài)加載 class 的時候(Class.forName("") 或 classloader loadClass時),classpath 找不到對應(yīng)的文件勉躺。 -
NoClassDefFoundError
:JVM runtime 拋出的 ERROR:compile time 可以找到的 class癌瘾,在 runtime 間(通過 new 或者方法調(diào)用 )無法加載。當然出現(xiàn)問題的情況也有多種:class 文件確實不在饵溅,被修改妨退,這個類依賴的類出現(xiàn)加載問題,或者靜態(tài)初始化拋出異常等蜕企。 -
NoSuchMethodError
:同樣是 JVM runtime ERROR咬荷,編譯期間可以找到的方法,runtime 期間找不到了轻掩。這次典型的包沖突導(dǎo)致NoSuchMethodError
幸乒。
3.0 jar 包沖突 - 本質(zhì)
讀完這個 重新看待Jar包沖突問題及解決方案 [3]收獲良多,總結(jié)部分筆記唇牧,對于沖突罕扎,存在兩個場景:
- 同一個Jar包出現(xiàn)了多個不同版本:相同的 jar 包(名 group artifict),不同的版本:例如開源 jar 包更新版本丐重。libthrift-0.9.2.jar libthrift-0.9.3.jar
- 同一個類出現(xiàn)在多個不同 Jar 包中:不同的 jar 包(名group artifict )腔召,同一個類(同樣的類限定詞)出現(xiàn)在不同的包中,例如:commons-lang 和 commons-lang3
這兩個場景的存在扮惦,會導(dǎo)致 JVM 加載到了錯誤的類臀蛛,導(dǎo)致與預(yù)期場景不一致出現(xiàn)上面描述的錯誤等,導(dǎo)致出現(xiàn) ClassNotFoundExpection
, NoClassDefFoundError
和 NoSuchMethodError
等異常崖蜜。
3.1 jar 包沖突 - maven 仲裁機制
因為 maven 的傳遞依賴機制浊仆,maven 引入依賴類似于圖的遍歷烙肺,從子往父溯源,引入所有相關(guān)依賴氧卧。這樣為開發(fā)節(jié)省了效率桃笙,但同時可能引入不同版本的 jar 包,導(dǎo)致在運行時出現(xiàn)包沖突沙绝。存在多個依賴搏明,maven 具體選擇引入哪個依賴,規(guī)范來源于仲裁機制闪檬,仲裁機制如下:
- 首先依據(jù)
<dependencyManagement>
中聲明的版本星著,此時下面的兩個原則都無效了 - 依據(jù)依賴樹中路徑最短的版本
- 路徑相同,則按照“第一聲明優(yōu)先”的原則進行仲裁粗悯,即選擇POM中最先聲明的版本
常見的解決依賴沖突的辦法有兩個:
-
<dependencyManagement>
是解決沖突的常用手段 -
<exclusions>
排除相關(guān)沖突依賴
下面這段配置取自 Zeppelin pom虚循,聲明選擇的依賴的 avro 版本,同時排除了部分依賴 如 netty样傍。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.avro</groupId>
<artifactId>avro</artifactId>
<version>${avro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.avro</groupId>
<artifactId>avro-ipc</artifactId>
<version>${avro.version}</version>
<exclusions>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>netty</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencyManagement>
3.2 jar 包沖突 - jar 包加載順序
還有一種沖突是同樣的類横缔,出現(xiàn)在不同的包里面,例如 A 和 B 包都有類 C衫哥,JVM 在加載 C 的時候到底是選擇 A 還是 B茎刚。這個選擇取決于:
- Jar 包所處的加載路徑,或者換個說法就是加載該 Jar 包的類加載器在 JVM 類加載器樹結(jié)構(gòu)中所處層級撤逢。例如 bootstrap classloader 還是 app classloader 路徑下膛锭。
- 文件系統(tǒng)的文件加載順序。這個因素很容易被忽略蚊荣,對于 linux 文件系統(tǒng)來說初狰, 可能是按照文件的 inode 排序決定。所以測試與生產(chǎn)環(huán)境是否一致很重要互例。
3.3 jar 包沖突 - 定位與解決
如果遇到ClassNotFoundExpection
, NoClassDefFoundError
和 NoSuchMethodError
奢入,有可能是包沖突造成的。
- 定位 jar 包: 根據(jù)日志查詢對應(yīng)的 class 所在 jar 包敲霍;可以直接在 IDEA 雙擊 shift 搜索俊马,并定位到 jar 包丁存,或者自己寫段腳本遍歷 jar 包搜索肩杈。
- 查看引入方: 通過
mvn dependency:tree -Dverbose -Dincludes=<groupId>:<artifactId>
查看是哪些地方引入。另外可以通過 IDEA Maven helper 插件來查看依賴沖突解寝。 - 解決沖突:可用 <excludes> 排除不需要的 Jar 包版本或者在依賴管理 <dependencyManagement> 中申明版本扩然。
[1] https://stackoverflow.com/questions/1457863/what-causes-and-what-are-the-differences-between-noclassdeffounderror-and-classn/1457879#1457879
[2] classnotfoundexception-vs-noclassdeffounderror
[3] 重新看待Jar包沖突問題及解決方案