spark standalone集群模式下Job運行出現ClassCastException異常的解決


在前一篇文章《Win7 下使用spark 對文件進行處理》中,搭建了一個win7的開發測試環境,生成了一個jar執行包,並能夠成功的在本機以多線程方式調試及運行,但將這個包分發到linux spark集群上,以standalone方式運行時,卻報如下異常:

18/05/22 17:13:22 ERROR Executor: Exception in task 0.0 in stage 0.0 (TID 0)
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
at java.io.ObjectStreamClass$FieldReflector.setObjFieldValues(ObjectStreamClass.java:2233)
at java.io.ObjectStreamClass.setObjFieldValues(ObjectStreamClass.java:1405)
at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2288)
at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2206)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2064)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1568)
at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2282)
at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2206)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2064)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1568)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:428)
at scala.collection.immutable.List$SerializationProxy.readObject(List.scala:479)

以下記錄解決此問題的思路與過程。

 

部署環境及變量設置:

3台ubuntu虛擬機,已經配置好ssh免密登錄《Linux系統中SSH安裝及配置免密碼登錄

master: 192.168.2.1
slave1: 192.168.2.2
slave2: 192.168.2.3

使用的是spark 2.2.1版本,spark在ubuntu上的standalone部署請參見Ubuntu18.04 下 Spark 2.4.3 standalone模式集群部署

/home/mytestzk/spark-2.2.1-bin-hadoop2.7

因為程序使用了redis作為存儲,故安裝了redis 最新的版本4.0.9

/home/mytestzk/redis-4.0.9

添加環境變量到/etc/profile

export SPARK_HOME=/home/mytestzk/spark-2.2.1-bin-hadoop2.7
export PATH=$PATH:$SPARK_HOME/bin

因spark可以不依賴hdfs,故雖然也安裝了hadoop,但此處可以先忽略,所以不需要啟動hdfs,如果文件是保存在hdfs中,則需要先啟動hdfs

修改redis的配置文件redis.conf如下參數:

bind 192.168.2.1
protected-mode no

修改spark的配置文件以使spark運行在standalone集群模式:

  • spark-defaults.conf
spark.master  spark://192.168.2.1:7077
  • spark-env.sh
export JAVA_HOME=/usr/lib/jvm/jdk1.8.0_151
export SPARK_MASTER_HOST=master
export SPARK_MASTER_PORT=7077
export SPARK_WORKER_MEMORY=2g
  • slaves
slave1
slave2

 

Spark 以standalone集群方式運行及問題產生

首先啟動redis

src/redis-server --protected-mode no

啟動spark,執行spark-2.2.1-bin-hadoop2.7目錄下命令,啟動spark集群

src/start-all.sh

 

提交作業,將已經打好的jar包及測試文件上傳到相應的目錄后,然后提交spark程序如下,因為是一個完整的sprint boot包,在pom文件中已經指定了啟動類,所以在運行spark-submit時不需要給出class參數

spark-submit --master spark://master:7077 --executor-memory 2g --total-executor-cores 2 /home/mytestzk/projects/myproject-1.0-SNAPSHOT.jar /mnt/WindowShare/holdings1.txt

運行異常如下所示:

 

問題解決:

經過上網搜索,有很多類似問題,有的意見是將spark-core_2.11的scope修改為provided,但驗證后不起作用,后來發現在SPARK有一個類似jira: https://issues.apache.org/jira/browse/SPARK-19938,其中有一段comment:

 

因為程序在standalone集群下以交互模式是可以運行成功的,

注:
以交互方式在standalonoe集群上測試,首先是啟動spark集群及redis server,然后在啟動spark-shell時,需要指定master參數,完整命令如下:
spark-shell --master spark://master:7077 --executor-memory 2g --total-executor-cores 2 --jars jedis-2.9.0.jar

如果啟動spark shell時沒有指定master地址,也可以正常啟動spark shell和執行spark shell中的程序,其實是啟動了spark的local模式,該模式僅在本機啟動一個進程,和spark集群沒有關系。

故可以排除代碼的問題,那問題就很可能在使用spart-submit提交程序時,環境不一致導致的,因為我使用的打包插件是spring-boot-maven-plugin,jar包里是使用的classloader是springframework loader,根據上面jira里comment的描述(avoid loading with different classloaders),很有可能問題就出在這里

 <build>
    <sourceDirectory>src/main/scala</sourceDirectory>
    <plugins>
      <plugin>
        <groupId>org.scala-tools</groupId>
        <artifactId>maven-scala-plugin</artifactId>
        <executions>
          <execution>
            <goals>
              <goal>compile</goal>
              <goal>testCompile</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <scalaVersion>${scala.version}</scalaVersion>
          <args>
            <arg>-target:jvm-1.5</arg>
          </args>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <executions>
          <execution>
            <goals>
              <goal>repackage</goal>
            </goals>
            <configuration>
              <!--<classifier>spring-boot</classifier>-->
              <mainClass>mytest.Import</mainClass>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

 

修改方案1:修改pom文件,不使用sprint boot插件:

    <build>
        <plugins>
            <plugin>
                <groupId>org.scala-tools</groupId>
                <artifactId>maven-scala-plugin</artifactId>
                <version>2.15.2</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>testCompile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.6.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.19</version>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>

        </plugins>
    </build>

這次打包出來的jar包只包含代碼的class文件,不包含第3放的類庫,因為這個spark程序需要使用的jedis-2.9.0.jar也沒有包含到輸出的jar包中,這個包只有10k不到,為了正確執行這個包,必須先把依賴的第3方庫也上傳到spark環境相應的目錄中,在調用spark-submit時,需要通過參數--jars指定依賴包的路徑,命令執行如下

spark-submit --class mytest.Import --jars /home/mytestzk/projects/jedis-2.9.0.jar  --master spark://master:7077 --executor-memory 2g --total-executor-cores 2 /home/mytestzk/projects/myproject-1.0-SNAPSHOT.jar /mnt/WindowShare/holdings1.txt

執行成功,問題解決!

 

修改方案2:

進一步,對於上面方案,如果依賴的第3放包比較少,也還可以,命令參數不會太長,但如果要依賴很多包,那么在spark-submit的jars參數中就需要列出來所有的包,中間用逗號分隔,這樣導致命令就比較長,很麻煩,那是否可以把所有需要依賴的第3放包放到一個專門的目錄中呢,查看spark文檔,答案是可以的,

修改spark的spark-default.conf,添加下面參數指定第3方庫的目錄,將依賴包jedis-2.9.0.jar拷貝到這個目錄

spark.executor.extraClassPath=/home/mytestzk/projects/*
spark.driver.extraClassPath=/home/mytestzk/projects/*

執行spark-submit命令如下

spark-submit --class mytest.Import   --master spark://master:7077 --executor-memory 2g --total-executor-cores 2 /home/mytestzk/projects/myproject-1.0-SNAPSHOT.jar /mnt/WindowShare/holdings1.txt

執行成功,問題解決!

 

修改方案3:

上面2種方案,需要單獨部署第3方依賴庫到spark執行環境,部署升級都很麻煩,如果能將程序所有依賴的包都打在一起,這樣部署升級都很方便,那好吧,再進一步,修改pom文件

<build>
    <sourceDirectory>src/main/scala</sourceDirectory>
    <plugins>
      <plugin>
        <groupId>org.scala-tools</groupId>
        <artifactId>maven-scala-plugin</artifactId>
        <executions>
          <execution>
            <goals>
              <goal>compile</goal>
              <goal>testCompile</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <scalaVersion>${scala.version}</scalaVersion>
          <args>
            <arg>-target:jvm-1.5</arg>
          </args>
        </configuration>
      </plugin>
      <plugin>
        <artifactId>maven-assembly-plugin</artifactId>
        <version>3.0.0</version>
        <configuration>
          <archive>
            <manifest>
              <mainClass>mytest.Import</mainClass>
            </manifest>
          </archive>
          <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
          </descriptorRefs>
        </configuration>
        <executions>
          <execution>
            <id>make-assembly</id>
            <phase>package</phase>
            <goals>
              <goal>single</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

使用maven-assembly-plugin插件,依賴的jar包會被解開並打入包中,而最開始使用的sprint-boot-maven-plugin插件,依賴的jar包是不會被解開的,使用這種方法,也不需要修改spark-defaults.conf文件了,重新打包並執行:

spark-submit --class mytest.Import   --master spark://master:7077 --executor-memory 2g --total-executor-cores 2 /home/mytestzk/projects/myproject-1.0-SNAPSHOT-jar-with-dependencies.jar /mnt/WindowShare/holdings1.txt

執行成功,問題解決!

 

但是這種方案,把所有依賴庫解包了,以class的形式和應用程序的class一起放在一個jar包,不像spring boot把依賴包以嵌入的jia包打在應用包里,那有沒有辦法,既可以使用spring boot的方式打包,又不會出現different classloader的問題呢,這個需要繼續研究,但到目前為止,所出現的異常是解決了。

思考這次遇到的問題,其實是從一開始,是希望能夠使用spint boot方式將所有依賴的庫打包成一個fat jar,但又不希望依賴庫被解包,如果開始是使用的maven assembly插件,就不會出現這個問題,也不會在這個問題上折騰了許久時間,但不折騰無收獲,希望對遇到類似問題的朋友有幫助。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM