zoukankan      html  css  js  c++  java
  • 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插件,就不会出现这个问题,也不会在这个问题上折腾了许久时间,但不折腾无收获,希望对遇到类似问题的朋友有帮助。

  • 相关阅读:
    89、Beego框架(上)——2020年07月30日22:05:40
    88、go框架有哪些-转载——2020年07月30日12:56:43
    86、go语言优势-转载——2020年07月27日16:08:04
    85、go开发配置环境——2020年07月27日13:08:27
    84、Linux命令——2020年07月27日12:51:44
    83、Linux知识-系统介绍、文件系统、远程操作——2020年07月27日12:47:14
    82、《给年轻人的极简金融课》-童哲——2020年7月23日20:17:49
    81、nodejs学习——2020年07月22日13:30:25
    无法启动此程序,因为计算机中丢失VCRUNTIME140.dll 尝试重新安装此程序以解决此问题
    关于js单页面实现跳转原理以及利用angularjs框架路由实现单页面跳转
  • 原文地址:https://www.cnblogs.com/benfly/p/9072957.html
Copyright © 2011-2022 走看看