提交Spark作業遇到的NoSuchMethodError問題總結

bjehp發表於2021-09-05

測試應用說明

測試的Spark應用實現了同步hive表到kafka的功能。具體處理流程:

  • 從 ETCD 獲取 SQL 語句和 Kafka 配置資訊
  • 使用 SparkSQL 讀取 Hive 資料表
  • 把 Hive 資料表的資料寫入 Kafka

應用使用etcd來儲存程式所需配置,通過拉取etcd的kv配置,來初始化sql語句和kafka配置的引數。

提交方式及相應的問題

  • 使用client模式,提交無依賴的jar包

提交命令

 /usr/local/spark-2.3.0-bin-2.8.2/bin/spark-submit \
    --name hive2kafka \
    --master yarn \
    --deploy-mode client \
    --driver-cores 1 \
    --driver-memory 2g \
    --num-executors 2 \
    --executor-cores 1 \
    --executor-memory 2g \
    --queue hive \
    --class com.ljh.spark.Hive2Kafka  \
    /data0/jianhong1/demo-v25/target/demo-1.0-SNAPSHOT.jar

應用執行失敗,driver端報錯:

Exception in thread "main" java.lang.NoClassDefFoundError: io/etcd/jetcd/Client
	at com.ljh.spark.EtcdUtil.getClient(EtcdUtil.java:27)
	at com.ljh.spark.EtcdUtil.get(EtcdUtil.java:46)
	at com.ljh.spark.Hive2Kafka.main(Hive2Kafka.java:60)
	...
Caused by: java.lang.ClassNotFoundException: io.etcd.jetcd.Client
	at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)

從報錯資訊可以看出,driver端沒有查詢到etcd的某個類,即沒有載入到etcd 的jar包。說明spark應用driver程式的classpath不包含etcd 的jar包。因此考慮打包fat jar,把etcd的jar包打入使用者提交的jar。

  • 使用client模式,提交包含依賴的jar包

提交命令

/usr/local/spark-2.3.0-bin-2.8.2/bin/spark-submit \
    --name hive2kafka \
    --master yarn \
    --deploy-mode client \
    --driver-cores 1 \
    --driver-memory 2g \
    --num-executors 2 \
    --executor-cores 1 \
    --executor-memory 2g \
    --queue hive \
    --class com.ljh.spark.Hive2Kafka  \
    /data0/jianhong1/demo-v25/target/demo-1.0-SNAPSHOT-jar-with-dependencies.jar

應用執行失敗,driver端報錯:

Exception in thread "main" java.lang.NoSuchMethodError: com.google.common.base.Preconditions.checkArgument(ZLjava/lang/String;CLjava/lang/Object;)V
	at io.grpc.Metadata$Key.validateName(Metadata.java:742)
	at io.grpc.Metadata$Key.<init>(Metadata.java:750)
	at io.grpc.Metadata$Key.<init>(Metadata.java:668)
	at io.grpc.Metadata$AsciiKey.<init>(Metadata.java:959)
	at io.grpc.Metadata$AsciiKey.<init>(Metadata.java:954)
	at io.grpc.Metadata$Key.of(Metadata.java:705)
	at io.grpc.Metadata$Key.of(Metadata.java:701)
	at io.etcd.jetcd.ClientConnectionManager.<clinit>(ClientConnectionManager.java:69)
	at io.etcd.jetcd.ClientImpl.<init>(ClientImpl.java:37)
	at io.etcd.jetcd.ClientBuilder.build(ClientBuilder.java:401)
	at com.ljh.spark.EtcdUtil.getClient(EtcdUtil.java:28)
	at com.ljh.spark.EtcdUtil.get(EtcdUtil.java:46)
	at com.ljh.spark.Hive2Kafka.main(Hive2Kafka.java:60)
    ...

從報錯資訊可以看出,應用沒有找到guava包Preconditions類checkArgument方法 。說明程式找到了guava包Preconditions類,但是這個類沒有找到checkArgument的某個構造方法。這種問題一般是由於jar包衝突,即程式載入了低版本的jar包,但是程式需要呼叫高版本jar包的某個方法,而這個方法低版本中沒有,就會出現上面的報錯NoSuchMethodError

因此考慮把程式中衝突的低版本guava包排除掉。通過檢查程式pom檔案的jar包依賴,明確新增適配etcd高版本的guava包,並把衝突的低版本的guava包排除掉。重新執行,發現依然出現上面的NoSuchMethodError報錯。

因此猜測低版本的guava包不是由於程式程式碼引入的,而是由spark提交機的本地包引入的。通過檢查spark提交機的本地包,查到引入了guava-14.0.1.jar,而程式中etcd依賴的guava包需要的版本為20+。說明應用使用了本地jar的低版本guava類,而沒有使用fat-jar的高版本guava類。由此推測出,spark應用driver端的類載入優先順序:本地jar > fat-jar。

  • 使用client模式,提交包含依賴的jar包,並新增driver-class 類路徑

提交命令

/usr/local/spark-2.3.0-bin-2.8.2/bin/spark-submit \
    --name hive2kafka \
    --master yarn \
    --deploy-mode client \
    --driver-class-path /data0/jianhong1/demo-v25/target/lib/guava-23.6-jre.jar:/data0/jianhong1/demo-v25/target/lib/protobuf-java-3.5.1.jar \
    --driver-cores 1 \
    --driver-memory 2g \
    --num-executors 2 \
    --executor-cores 1 \
    --executor-memory 2g \
    --queue hive \
    --class com.ljh.spark.Hive2Kafka  \
    /data0/jianhong1/demo-v25/target/demo-1.0-SNAPSHOT-jar-with-dependencies.jar

程式正常執行,不再出現NoSuchMethodError報錯。由此推測出,spark應用driver端的類載入優先順序:driver-class-path 配置 > 本地jar。

  • 使用cluster模式,提交包含依賴的jar包

提交命令

/usr/local/spark-2.3.0-bin-2.8.2/bin/spark-submit \
    --name hive2kafka \
    --master yarn \
    --deploy-mode cluster \
    --driver-cores 1 \
    --driver-memory 2g \
    --num-executors 2 \
    --executor-cores 1 \
    --executor-memory 2g \
    --queue hive \
    --class com.ljh.spark.Hive2Kafka  \
    /data0/jianhong1/demo-v25/target/demo-1.0-SNAPSHOT-jar-with-dependencies.jar

應用執行失敗,報錯資訊:

	 diagnostics: User class threw exception: java.lang.NoSuchMethodError: com.google.common.base.Preconditions.checkArgument(ZLjava/lang/String;CLjava/lang/Object;)V
	at io.grpc.Metadata$Key.validateName(Metadata.java:742)
	at io.grpc.Metadata$Key.<init>(Metadata.java:750)
	at io.grpc.Metadata$Key.<init>(Metadata.java:668)
	at io.grpc.Metadata$AsciiKey.<init>(Metadata.java:959)
	at io.grpc.Metadata$AsciiKey.<init>(Metadata.java:954)
	at io.grpc.Metadata$Key.of(Metadata.java:705)
	at io.grpc.Metadata$Key.of(Metadata.java:701)
	at io.etcd.jetcd.ClientConnectionManager.<clinit>(ClientConnectionManager.java:69)
	at io.etcd.jetcd.ClientImpl.<init>(ClientImpl.java:37)
	at io.etcd.jetcd.ClientBuilder.build(ClientBuilder.java:401)
	at com.ljh.spark.EtcdUtil.getClient(EtcdUtil.java:28)
	at com.ljh.spark.EtcdUtil.get(EtcdUtil.java:46)
	at com.ljh.spark.Hive2Kafka.main(Hive2Kafka.java:66)
    ...

從報錯資訊可以看出,應用找到了guava包Preconditions類,但是在這個類中沒有找到checkArgument的某個構造方法。

因此考慮在提交作業時明確指出etcd所依賴的高版本guava包。於是提交引數新增了 --jars hdfs:/user/jianhong1/jars/guava-23.6-jre.jar,hdfs:/user/jianhong1/jars/protobuf-java-3.5.1.jar \,重新執行後依然報上面的錯。說明 --jar 引數只是負責把jar包拷貝到執行作業的伺服器上,但是沒把指定的jar包加到類路徑。

  • 使用cluster模式,提交包含依賴的jar包,並新增driver 和executor 類路徑。

提交命令

/usr/local/spark-2.3.0-bin-2.8.2/bin/spark-submit \
    --name hive2kafka \
    --master yarn \
    --deploy-mode cluster \
    --driver-cores 1 \
    --driver-memory 2g \
    --num-executors 2 \
    --executor-cores 1 \
    --executor-memory 2g \
    --queue hive \
    --class com.ljh.spark.Hive2Kafka  \
    --conf spark.driver.extraClassPath=guava-23.6-jre.jar:protobuf-java-3.5.1.jar \
    --conf spark.executor.extraClassPath=guava-23.6-jre.jar:protobuf-java-3.5.1.jar \
    --jars hdfs:/user/jianhong1/jars/guava-23.6-jre.jar,hdfs:/user/jianhong1/jars/protobuf-java-3.5.1.jar \
    /data0/jianhong1/demo-v25/target/demo-1.0-SNAPSHOT-jar-with-dependencies.jar

通過增加guava包的driver 和executor 類路徑的配置後,應用成功執行!

總結

本文介紹了client 方式和cluster 方式提交Spark 應用時所遇到的NoSuchMethodError 問題,及相應的解決方案。通過實際測試得到結論: spark應用的類載入優先順序:--driver-class-path--executor-class-path配置 > 本地jar > fat-jar。

參考

The --jars argument only transports the jars to each machine in the cluster. It does NOT tell spark to use them in the class path search. The --driver-class-path (or similar arguments or config parameters) are also required.
--jars 引數只是用於傳輸 jar 包到叢集的 Executor 和 Driver 的伺服器上,它不會告知 spark 應用在哪個類路徑下使用這些jar包。因此,--driver-class-path或--executor-class-path引數也是必需的,用於配置 driver 和 executor 的類路徑。  
spark on yarn執行時會載入的jar包有如下:
spark-submit中指定的--jars
$SPARK_HOME/jars下的jar包
yarn提供的jar包
spark-submit通過引數spark.driver/executor.extraClassPath指定的jar包

相關文章