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

  • 相关阅读:
    arcgis api 3.x for js 入门开发系列八聚合效果(附源码下载)
    arcgis api 3.x for js 入门开发系列七图层控制(附源码下载)
    arcgis api 3.x for js 入门开发系列六地图分屏对比(附源码下载)
    arcgis api 3.x for js 入门开发系列五地图态势标绘(附源码下载)
    arcgis api 3.x for js 入门开发系列四地图查询(附源码下载)
    Java里面获取当前服务器的IP地址
    Flutter at Google I/O 2018
    Modbus RTU 协议使用汇总
    plsql 创建表空间、用户、赋予权限
    Oracle:ODP.NET Managed 小试牛刀
  • 原文地址:https://www.cnblogs.com/benfly/p/9072957.html
Copyright © 2011-2022 走看看