基于Spark SQL 读写Oracle 的简单案例分析常见问题

 


1      概述

本文主要内容包含Spark SQL读写Oracle表数据的简单案例,并针对案例中比较常见的几个问题给出解决方法。

最后从常见的java.lang.ClassNotFoundException(无法找到驱动类)的异常问题出发,分析相关的几种解决方法,以及各个解决方法之间的异同点。

2      案例中比较常见问题及其解决方法

2.1     启动

首先查看Spark 官网给出的SparkSQL的编程指南部分(http://spark.apache.org/docs/latest/sql-programming-guide.html)的JDBC To Other Databases 内容。参考命令:

SPARK_CLASSPATH=postgresql-9.3-1102-jdbc41.jar bin/spark-shell

对应写出访问 Oracle的命令,如下:

SPARK_CLASSPATH=$SPARK_HOME/ojdbc14.jar  bin/spark-shell --master local

其中,CLASSPATH相关内容会在后一章节给出详细分析,在此仅针对其他一些常见问题给出解决方法。

启动过程如下(部分字符串已经被替换,如:$SPARK_HOME):

[hdfs@masternode spark-1.5.2-bin-hadoop2.6]$ SPARK_CLASSPATH=$SPARK_HOME/lib/ojdbc14.jar bin/spark-shell --master local

……

Welcome to

      ____              __

     / __/__  ___ _____/ /__

    _\ \/ _ \/ _ `/ __/  '_/

   /___/ .__/\_,_/_/ /_/\_\   version 1.5.2

      /_/

 

Using Scala version 2.10.4 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_71)

Type in expressions to have them evaluated.

Type :help for more information.

16/04/18 11:56:35 INFO spark.SparkContext: Running Spark version 1.5.2

16/04/18 11:56:35 WARN spark.SparkConf:

SPARK_CLASSPATH was detected (set to '$SPARK_HOME/lib/ojdbc14.jar').

This is deprecated in Spark 1.0+.

 

Please instead use:

- ./spark-submit with --driver-class-path to augment the driver classpath

 - spark.executor.extraClassPath to augment the executor classpath

 

16/04/18 11:56:35 WARN spark.SparkConf: Setting 'spark.executor.extraClassPath' to '$SPARK_HOME/lib/ojdbc14.jar' as a work-around.

16/04/18 11:56:35 WARN spark.SparkConf: Setting 'spark.driver.extraClassPath' to '$SPARK_HOME/lib/ojdbc14.jar' as a work-around.

……

16/04/18 11:56:51 INFO server.AbstractConnector: Started [email protected]:4040

16/04/18 11:56:51 INFO util.Utils: Successfully started service 'SparkUI' on port 4040.

16/04/18 11:56:51 INFO ui.SparkUI: Started SparkUI at http://192.168.149.86:4040

……

16/04/18 11:56:57 INFO repl.SparkILoop: Created sql context (with Hive support)..

SQL context available as sqlContext.

 

scala>


































下面给出简单读写案例中的常见问题及其解决方法。

2.2     访问表数据时报找不到合适的驱动

scala> val url = "jdbc:oracle:thin:@xx.xx.xx.xx:1521:dbsid"

url: String = jdbc:oracle:thin:@xx.xx.xx.xx:1521:dbsid

 

// 通过format指定访问jdbc,通过options指定访问时的参数,然后load加载数据

scala> val jdbcDF = sqlContext.read.format("jdbc").options( Map( "url" -> url, "dbtable" -> "MyTable", "user" -> "username", "password" -> " password ")).load()

java.sql.SQLException: No suitable driver found for jdbc:oracle:thin:@xx.xx.xx.xx:1521:dbsid

        at java.sql.DriverManager.getConnection(DriverManager.java:596)

        at java.sql.DriverManager.getConnection(DriverManager.java:187)

        at org.apache.spark.sql.execution.datasources.jdbc.JDBCRDD$$anonfun$getConnector$1.apply(JDBCRDD.scala:188)

        at org.apache.spark.sql.execution.datasources.jdbc.JDBCRDD$$anonfun$getConnector$1.apply(JDBCRDD.scala:181)

        at org.apache.spark.sql.execution.datasources.jdbc.JDBCRDD$.resolveTable(JDBCRDD.scala:121)

        at org.apache.spark.sql.execution.datasources.jdbc.JDBCRelation.(JDBCRelation.scala:91)

        at org.apache.spark.sql.execution.datasources.jdbc.DefaultSource.createRelation(DefaultSource.scala:60)

        at org.apache.spark.sql.execution.datasources.ResolvedDataSource$.apply(ResolvedDataSource.scala:125)

        at org.apache.spark.sql.DataFrameReader.load(DataFrameReader.scala:114)

……

报错信息为:

java.sql.SQLException: No suitable driver found for jdbc:oracle:thin:@xx.xx.xx.xx:1521:dbsid

即报错提示无法找到合适的驱动时,此时可以通过在options方法中指定”driver”属性为具体的驱动类来解决,如下所示:

scala> val jdbcDF = sqlContext.read.format("jdbc").options( Map( "url" -> url, "dbtable" -> "TEST_TABLE", "user" -> "userName", "password" -> "password", "driver" -> "oracle.jdbc.driver.OracleDriver")).load()

jdbcDF: org.apache.spark.sql.DataFrame = [ID: decimal(0,-127), XX: string, XX: string, XX: string, XX: decimal(0,-127)]

注意:在1.5版本中,当Oracle表字段为Number时,对应DataType为decimal,此时会由于scala的精度断言抛出异常——可以在stackoverflow网站查找该异常——应该是个bug,在1.6中应该已经解决。有兴趣可以试下下。——如果继续show数据的话,会抛出该异常。

补充:手动加载驱动类也可以解决。

2.3     访问表数据时报无效调用参数

另外,当属性名写错时的错误信息:

——由于在通常写jdbc的连接时,使用username标识用户名,(比如在sqoop工具中),在SparkSQL中需要使用user,否则会报以下错误(可以在官网或API的描述中查找相关属性名):

scala> val jdbcDF = sqlContext.read.format("jdbc").options( Map( "url" -> url, "dbtable" -> "TEST_TABLE", "username" -> "userName", "password" -> "password")).load()

java.sql.SQLException: invalid arguments in call

        at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:112)

        at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:146)

        at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:208)

        at oracle.jdbc.driver.T4CConnection.logon(T4CConnection.java:236)

当出现invalid arguments in call错误,表示调用时使用了无效参数,可以检查options中的参数名是否写错,尤其是user是否写成了username。

下面解析最常见的无法找到驱动类的问题及其解决方法。

3      无法找到无法找到驱动类的问题及其解决方法

扩展知识:JVM的类加载机制——该知识是解决这一类问题的根本方法。

下面的解决方法主要从JVM进程的classpath配置出发去分析。

在Spark框架中,为JVM进程的classpath配置提供了以下几种方式:

1.        使用环境变量SPARK_CLASSPATH:将jar包放置在类查找路径,比如前面的SPARK_CLASSPATH,设置该变量后可以查看4040(默认应用监控端口)的环境设置,此时需要在driver和executor的classpath的路径中放置所需的jar包。

由前面启动过程中的日志可知,这种方式已经废弃,对应的方式是直接设置driver和executor的classpath相关的配置属性。

2.        使用配置属性分别设置driver和executor执行时加载jar包的路径。

a)        spark.executor.extraClassPath

b)       spark.driver.extraClassPath

取代使用环境变量进行配置的方法。由于通常情况下,driver和executor的进程不在同一个节点上,因此分别给出各自的配置属性。

和前面使用环境变量进行配置的方式一样,在设置相应的classpath路径之后,需要将对应的jar包部署到各个节点上的该classpath路径下。

说明:上述两种方法都是通过设置JVM进程的classpath属性,为进程提供jar包查找路径的。而对应的jar包,需要人为地部署到各个节点的对应路径下。

补充:另外可以通过提交命令的命令行选项--driver-class-path来设置driver端进程的classpath,原理类似,因此不作为单独一种。

注意:--conf命令项方式设置时,--confPROP=VALUE的等号中间不要加空格….

3.        除了前面两种方式外,Spark框架还提供了第三种方法,可以通过提交命令(spark-submit或spark-shell)的命令行选项--jars,自动上传、下载所需jar包,并同时将下载的jar包放入JVM进程的classpath路径中。

说明:通过该命令行选项设置的jar包,会通过http服务(注意,该服务在driver端启动后才会启动)上传jar包,并在executor端执行时下载该jar包,然后放入classpath。即,此时不需要手动部署到各个节点上,但每个提交的应用都会增加jar包上传、下载的网络IO、磁盘IO开销。

下面针对提交时的--master选项、--deploymode的选项分别进行解析。

不同选项的简单说明如下:

1.       当--master选项为local时,对应为in-process方式,此时仅一个进程(local-cluster时也对应一个进程,内部通过实例模拟),因此,对应的classpath实际上使用的都是driver短对应进程的classpath。即只需要配置driver的classpath即可。

2.       当--master选项为集群的MasterURL(本文主要基于Standalone模式的集群)时,对应driver和executor是以不同的进程方式启动,因此需要分别进行设置。并且,在不同的部署模式(--deploymode)下也会有细节上的差异(本质上还是根据JVM类加载机制):

a)        --deploymode为client时:driver进程在当前提交节点上启动

b)       --deploymode为cluster时:driver进程提交到集群中,由集群调度Master负责分配节点,并在分配的节点上启动driver进程。

说明:不同的部署模式和jar包的上传、下载有关,即在使用--jars方式时会有所差异,其关键点在于,jar包的上传、下载是通过driver进程启动过程中启动的http服务来完成的,当指定的jar包是以本地文件系统的路径提供时,在另一个节点启动的driver进程中的http服务根据该路径上传jar包时,也会根据本地文件系统指定的路径去上传,所以此时必须保证由Master节点分配给driver的节点,对应的该路径上也存在需要上传的jar包。

因此,建议在使用cluster部署模式提交应用程序时,所使用的路径尽可能与节点无关,比如使用hdfs文件系统的路径等。

3.1    测试类的设计

关于下面使用的SparkTest.jar中的com.TestClass测试类,分别在driver端和executor端同时访问Oracle的表数据,即两处都需要加载Oracle的驱动器类。

通过这种方式,方便分别测试driver端和executor端与各自的classpath配置及其jar包放置等相关的内容。

3.1.1       包含Driver与Executor执行逻辑的代码

如下所示:

object TestJarwithOracle {

  // 硬编码

  val url = "jdbc:oracle:thin:@xx.xx.xx.xx:1521:dbsid"

  val user = "userName"

  val password = "password"

  val dbtable = "TABLE_TEST"

 

  def main(args: Array[String]) {

    val conf = new SparkConf().setAppName(this.getClass.getSimpleName)

    val sc = new SparkContext(conf)

    val sqlContext = new SQLContext(sc)

 

    val logRDD = sc.parallelize( List( ("name", "action", "date", 1), ("name", "action", "date", 2) ))

    // 处理逻辑:在Driver端

    deleteRecodes("date")

 

    // 处理逻辑:位于Executor端

    logRDD.foreachPartition(insertInto)

    sc.stop()

  }

 

  def deleteRecodes(date: String): Unit = {

    var conn: Connection = null

    var ps: PreparedStatement = null

    val sql = s"delete from $dbtable where log_date in ('$date')"

    println(sql)

    try {

      Class.forName("oracle.jdbc.driver.OracleDriver")

      conn = DriverManager.getConnection(url, user, password)

      ps = conn.prepareStatement(sql)

      ps.executeUpdate()

    } catch {

      case e: Exception => e.printStackTrace

    } finally {

      if (ps != null) {

        ps.close()

      }

      if (conn != null) {

        conn.close()

      }

    }

  }

 

  def insertInto(iterator: Iterator[(String, String, String, Int)]): Unit = {

    var conn: Connection = null

    var ps: PreparedStatement = null

    val sql = s"insert into $dbtable( USER_ACTION, USER_NAME, LOG_DATE, ACTION_CNT) values (?, ?, ?, ?)"

 

    try {

      //conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/spark", "root", "123456")

      Class.forName("oracle.jdbc.driver.OracleDriver")

      conn = DriverManager.getConnection(url, user, password)

      iterator.foreach(data => {

        ps = conn.prepareStatement(sql)

        ps.setString(1, data._1)

        ps.setString(2, data._2)

        ps.setString(3, data._3)

        ps.setInt(4, data._4)

        // ps.setInt(3, data._3)

        ps.executeUpdate()

      }

      )

    } catch {

      case e: Exception => e.printStackTrace

    } finally {

      if (ps != null) {

        ps.close()

 

      }

      if (conn != null) {

        conn.close()

      }

    }

  }

}

3.1.2       代码说明

前面给出了从oracle(其他数据库基本一样)中读取的简单案例,对应insert的话,和普通oracle表的insert类似,需要注意的是连接的创建位置(可以参考Spark官网的流部分)。

大致原理简单描述如下:

1.        Driver端创建的对象需要序列化到Executor端才能使用,当特定对象(如数据库连接)与具体节点绑定(如hostname绑定)时,即使序列化成功,在Executor端反序列化后对象也不能使用(比如反序列化时的初始化失败或hostname不同导致连接等无法使用等)

2.        一个分区对应一个task,Executor上的执行单位为task。

在一个执行单位中复用,即针对分区提供一个连接——可以复用连接池。

比如:rdd.foreachPartition(insertInto)

对应的function :insertInto,和普通的数据库insert方式是一样的(可以采用批量插入),针对每个分区Partition,然后获取或构建一个数据库连接,通过该连接将分区的Iterator数据插入到数据库表。

3.2     master为local时

首先查看下SparkSubmit提交应用程序时,一些不支持的组合形式,对应代码如下所示:

private[deploy] def prepareSubmitEnvironment(args: SparkSubmitArguments)

……

case (LOCAL, CLUSTER) =>
  printErrorAndExit("Cluster deploy mode is not compatible with master \"local\"")

,,,,,,

即,在使用local与local-cluster这种local方式时,不支持以CLUSTER的部署模式提交应用程序。

因此以下对应都是在CLIENT的部署模式提交应用程序。

3.2.1       环境变量方式

1.        命令:

SPARK_CLASSPATH=$SPARK_HOME/ojdbc14.jar….

2.        说明:

指定SPARK_CLASSPATH时,相当于同时指定driver和executor的classpath,根据前面的分析,实际上local模式下只需要设置driver端的classpath即可,同时需要手动在该路径下方式所需的jar包,否则会抛出驱动类无法找到的异常。

参考前面启动章节的启动日志中的红色斜体部分,表示的是SPARK_CLASSPATH已经被废弃,建议使用斜体部分的对应配置属性进行替换。

Please instead use:

- ./spark-submit with --driver-class-path to augment the driver classpath

 - spark.executor.extraClassPath to augment the executor classpath

 

16/04/18 11:56:35 WARN spark.SparkConf: Setting 'spark.executor.extraClassPath' to '$SPARK_HOME/lib/ojdbc14.jar' as a work-around.

16/04/18 11:56:35 WARN spark.SparkConf: Setting 'spark.driver.extraClassPath' to '$SPARK_HOME/lib/ojdbc14.jar' as a work-around.

 

3.        测试方式一:在环境变量的路径下不存在所需jar包,driver和executor端加载类同时异常,执行命令及其异常日志如下所示:

[hdfs@nodemaster spark-1.5.2-bin-hadoop2.6]$ SPARK_CLASSPATH=$SPARK_HOME/ojdbc14.jar $SPARK_HOME/bin/spark-submit  --master local \

>   --deploy-mode client \

>   --driver-memory 2g \

>   --driver-cores 1 \

>   --total-executor-cores 2 \

>   --executor-memory 4g \

>   --conf "spark.ui.port"=4081 \

> --class com.mb.TestJarwithOracle \

> /tmp/test/Spark15.jar

16/04/26 10:31:39 INFO spark.SparkContext: Running Spark version 1.5.2

16/04/26 10:31:40 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable

16/04/26 10:31:40 WARN spark.SparkConf:

SPARK_CLASSPATH was detected (set to '/ojdbc14.jar').

This is deprecated in Spark 1.0+.

 

Please instead use:

 - ./spark-submit with --driver-class-path to augment the driver classpath

 - spark.executor.extraClassPath to augment the executor classpath

       

16/04/26 10:31:40 WARN spark.SparkConf: Setting 'spark.executor.extraClassPath' to '/ojdbc14.jar' as a work-around.

16/04/26 10:31:40 WARN spark.SparkConf: Setting 'spark.driver.extraClassPath' to '/ojdbc14.jar' as a work-around.

16/04/26 10:31:40 INFO spark.SecurityManager: Changing view acls to: hdfs

16/04/26 10:31:40 INFO spark.SecurityManager: Changing modify acls to: hdfs

16/04/26 10:31:40 INFO spark.SecurityManager: SecurityManager: authentication disabled; ui acls disabled; users with view permissions: Set(hdfs); users with modify permissions: Set(hdfs)

16/04/26 10:31:41 INFO slf4j.Slf4jLogger: Slf4jLogger started

16/04/26 10:31:41 INFO Remoting: Starting remoting

16/04/26 10:31:42 INFO Remoting: Remoting started; listening on addresses :[akka.tcp://[email protected]:32898]

16/04/26 10:31:42 INFO util.Utils: Successfully started service 'sparkDriver' on port 32898.

16/04/26 10:31:42 INFO spark.SparkEnv: Registering MapOutputTracker

16/04/26 10:31:42 INFO spark.SparkEnv: Registering BlockManagerMaster

16/04/26 10:31:42 INFO storage.DiskBlockManager: Created local directory at /tmp/blockmgr-43894e1f-4546-477c-91f9-766179306112

16/04/26 10:31:42 INFO storage.MemoryStore: MemoryStore started with capacity 1060.3 MB

16/04/26 10:31:42 INFO spark.HttpFileServer: HTTP File server directory is /tmp/spark-0294727b-0b57-48ff-9f36-f441fa3604aa/httpd-cec03f6e-ca66-49b8-94b9-39427a86ed65

16/04/26 10:31:42 INFO spark.HttpServer: Starting HTTP Server

16/04/26 10:31:42 INFO server.Server: jetty-8.y.z-SNAPSHOT

16/04/26 10:31:42 INFO server.AbstractConnector: Started [email protected]:57115

16/04/26 10:31:42 INFO util.Utils: Successfully started service 'HTTP file server' on port 57115.

16/04/26 10:31:42 INFO spark.SparkEnv: Registering OutputCommitCoordinator

16/04/26 10:31:57 INFO server.Server: jetty-8.y.z-SNAPSHOT

16/04/26 10:31:57 INFO server.AbstractConnector: Started [email protected]:4081

16/04/26 10:31:57 INFO util.Utils: Successfully started service 'SparkUI' on port 4081.

16/04/26 10:31:57 INFO ui.SparkUI: Started SparkUI at http://192.168.149.86:4081

16/04/26 10:31:57 INFO spark.SparkContext: Added JAR file:/tmp/test/Spark15.jar at http://192.168.149.86:57115/jars/Spark15.jar with timestamp 1461637917682

16/04/26 10:31:57 WARN metrics.MetricsSystem: Using default name DAGScheduler for source because spark.app.id is not set.

16/04/26 10:31:57 INFO executor.Executor: Starting executor ID driver on host localhost

16/04/26 10:31:58 INFO util.Utils: Successfully started service 'org.apache.spark.network.netty.NettyBlockTransferService' on port 23561.

16/04/26 10:31:58 INFO netty.NettyBlockTransferService: Server created on 23561

16/04/26 10:31:58 INFO storage.BlockManagerMaster: Trying to register BlockManager

16/04/26 10:31:58 INFO storage.BlockManagerMasterEndpoint: Registering block manager localhost:23561 with 1060.3 MB RAM, BlockManagerId(driver, localhost, 23561)

16/04/26 10:31:58 INFO storage.BlockManagerMaster: Registered BlockManager

16/04/26 10:31:59 INFO scheduler.EventLoggingListener: Logging events to hdfs://nodemaster:8020/user/hdfs/sparklogs/local-1461637917735

delete from TEST_TABLE where log_date in ('date')

java.lang.ClassNotFoundException: oracle.jdbc.driver.OracleDriver

        at java.net.URLClassLoader$1.run(URLClassLoader.java:366)

        at java.net.URLClassLoader$1.run(URLClassLoader.java:355)

        at java.security.AccessController.doPrivileged(Native Method)

        at java.net.URLClassLoader.findClass(URLClassLoader.java:354)

        at java.lang.ClassLoader.loadClass(ClassLoader.java:425)

        at java.lang.ClassLoader.loadClass(ClassLoader.java:358)

        at java.lang.Class.forName0(Native Method)

        at java.lang.Class.forName(Class.java:191)

        at com.mb.TestJarwithOracle$.deleteRecodes(TestJarwithOracle.scala:38)

        at com.mb.TestJarwithOracle$.main(TestJarwithOracle.scala:27)

        at com.mb.TestJarwithOracle.main(TestJarwithOracle.scala)

        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)

        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

        at java.lang.reflect.Method.invoke(Method.java:606)

        at org.apache.spark.deploy.SparkSubmit$.org$apache$spark$deploy$SparkSubmit$$runMain(SparkSubmit.scala:674)

        at org.apache.spark.deploy.SparkSubmit$.doRunMain$1(SparkSubmit.scala:180)

        at org.apache.spark.deploy.SparkSubmit$.submit(SparkSubmit.scala:205)

        at org.apache.spark.deploy.SparkSubmit$.main(SparkSubmit.scala:120)

        at org.apache.spark.deploy.SparkSubmit.main(SparkSubmit.scala)

16/04/26 10:31:59 INFO spark.SparkContext: Starting job: foreachPartition at TestJarwithOracle.scala:28

16/04/26 10:31:59 INFO scheduler.DAGScheduler: Got job 0 (foreachPartition at TestJarwithOracle.scala:28) with 1 output partitions

16/04/26 10:31:59 INFO scheduler.DAGScheduler: Final stage: ResultStage 0(foreachPartition at TestJarwithOracle.scala:28)

16/04/26 10:31:59 INFO scheduler.DAGScheduler: Parents of final stage: List()

16/04/26 10:31:59 INFO scheduler.DAGScheduler: Missing parents: List()

16/04/26 10:32:00 INFO scheduler.DAGScheduler: Submitting ResultStage 0 (ParallelCollectionRDD[0] at parallelize at TestJarwithOracle.scala:26), which has no missing parents

16/04/26 10:32:00 INFO storage.MemoryStore: ensureFreeSpace(1200) called with curMem=0, maxMem=1111794647

16/04/26 10:32:00 INFO storage.MemoryStore: Block broadcast_0 stored as values in memory (estimated size 1200.0 B, free 1060.3 MB)

16/04/26 10:32:00 INFO storage.MemoryStore: ensureFreeSpace(851) called with curMem=1200, maxMem=1111794647

16/04/26 10:32:00 INFO storage.MemoryStore: Block broadcast_0_piece0 stored as bytes in memory (estimated size 851.0 B, free 1060.3 MB)

16/04/26 10:32:00 INFO storage.BlockManagerInfo: Added broadcast_0_piece0 in memory on localhost:23561 (size: 851.0 B, free: 1060.3 MB)

16/04/26 10:32:00 INFO spark.SparkContext: Created broadcast 0 from broadcast at DAGScheduler.scala:861

16/04/26 10:32:00 INFO scheduler.DAGScheduler: Submitting 1 missing tasks from ResultStage 0 (ParallelCollectionRDD[0] at parallelize at TestJarwithOracle.scala:26)

16/04/26 10:32:00 INFO scheduler.TaskSchedulerImpl: Adding task set 0.0 with 1 tasks

16/04/26 10:32:00 INFO scheduler.TaskSetManager: Starting task 0.0 in stage 0.0 (TID 0, localhost, PROCESS_LOCAL, 2310 bytes)

16/04/26 10:32:00 INFO executor.Executor: Running task 0.0 in stage 0.0 (TID 0)

16/04/26 10:32:00 INFO executor.Executor: Fetching http://192.168.149.86:57115/jars/Spark15.jar with timestamp 1461637917682

16/04/26 10:32:00 INFO util.Utils: Fetching http://192.168.149.86:57115/jars/Spark15.jar to /tmp/spark-0294727b-0b57-48ff-9f36-f441fa3604aa/userFiles-9b936a62-13aa-4ac4-8c26-caabe7bd4367/fetchFileTemp8857984169855770119.tmp

16/04/26 10:32:00 INFO executor.Executor: Adding file:/tmp/spark-0294727b-0b57-48ff-9f36-f441fa3604aa/userFiles-9b936a62-13aa-4ac4-8c26-caabe7bd4367/Spark15.jar to class loader

java.lang.ClassNotFoundException: oracle.jdbc.driver.OracleDriver

        at java.net.URLClassLoader$1.run(URLClassLoader.java:366)

        at java.net.URLClassLoader$1.run(URLClassLoader.java:355)

        at java.security.AccessController.doPrivileged(Native Method)

        at java.net.URLClassLoader.findClass(URLClassLoader.java:354)

        at java.lang.ClassLoader.loadClass(ClassLoader.java:425)

        at java.lang.ClassLoader.loadClass(ClassLoader.java:358)

        at java.lang.Class.forName0(Native Method)

        at java.lang.Class.forName(Class.java:191)

        at com.mb.TestJarwithOracle$.insertInto(TestJarwithOracle.scala:61)

        at com.mb.TestJarwithOracle$$anonfun$main$1.apply(TestJarwithOracle.scala:28)

        at com.mb.TestJarwithOracle$$anonfun$main$1.apply(TestJarwithOracle.scala:28)

        at org.apache.spark.rdd.RDD$$anonfun$foreachPartition$1$$anonfun$apply$29.apply(RDD.scala:902)

        at org.apache.spark.rdd.RDD$$anonfun$foreachPartition$1$$anonfun$apply$29.apply(RDD.scala:902)

        at org.apache.spark.SparkContext$$anonfun$runJob$5.apply(SparkContext.scala:1850)

        at org.apache.spark.SparkContext$$anonfun$runJob$5.apply(SparkContext.scala:1850)

        at org.apache.spark.scheduler.ResultTask.runTask(ResultTask.scala:66)

        at org.apache.spark.scheduler.Task.run(Task.scala:88)

        at org.apache.spark.executor.Executor$TaskRunner.run(Executor.scala:214)

        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)

        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)

        at java.lang.Thread.run(Thread.java:745)

16/04/26 10:32:00 INFO executor.Executor: Finished task 0.0 in stage 0.0 (TID 0). 915 bytes result sent to driver

16/04/26 10:32:00 INFO scheduler.TaskSetManager: Finished task 0.0 in stage 0.0 (TID 0) in 235 ms on localhost (1/1)

两个异常分别对应driver端和executor端。

4.        测试方式二:在环境变量的路径下存在所需jar包,driver和executor端加载类正常。

环境变量同时设置了driver端和executor端的classpath,只要该路径下有jar包,驱动类即可加载。

下面通过配置属性分别进行配置并测试。

3.2.2       配置属性方式

1.        使用配置属性提交应用程序的命令:

         $SPARK_HOME/bin/spark-submit  --master spark://masternode:7078 \[h1] 

           --conf "spark.executor.extraClassPath"="$SPARK_HOME/lib/ojdbc14.jar" \

           --conf "spark.driver.extraClassPath"="$SPARK_HOME/lib/ojdbc14.jar"   \

           --conf "spark.ui.port"=4061 \

           --class com.TestClass \

           /tmp/test/SparkTest.jar

通过前面的分析,在local下,只"spark.driver.extraClassPath"有效。

2.        说明:

此时,配置属性只是指定jar包在classpath指定的路径下,但没有手动将所需的jar包部署到该路径下,因此加载驱动器类时会抛出java.lang.ClassNotFoundException:oracle.jdbc.driver.OracleDriver的异常。

3.        执行日志如下所示[h2] :

16/04/14 14:23:18 INFO spark.MapOutputTrackerMaster: Size of output statuses for shuffle 0 is 154 bytes

16/04/14 14:23:19 WARN scheduler.TaskSetManager: Lost task 0.0 in stage 1.0 (TID 2, 192.168.149.98): java.lang.ClassNotFoundException: oracle.jdbc.driver.OracleDriver

        at java.net.URLClassLoader$1.run(URLClassLoader.java:366)

        at java.net.URLClassLoader$1.run(URLClassLoader.java:355)

        at java.security.AccessController.doPrivileged(Native Method)

        at java.net.URLClassLoader.findClass(URLClassLoader.java:354)

        at java.lang.ClassLoader.loadClass(ClassLoader.java:425)

        at java.lang.ClassLoader.loadClass(ClassLoader.java:358)

        at java.lang.Class.forName0(Native Method)

        at java.lang.Class.forName(Class.java:191)

        at com.TestClass$.insertInto(WebLogExtractor.scala:48)

        at com.TestClass$$anonfun$main$1.apply(WebLogExtractor.scala:43)

        at com.TestClass$$anonfun$main$1.apply(WebLogExtractor.scala:43)

        at org.apache.spark.rdd.RDD$$anonfun$foreachPartition$1$$anonfun$apply$29.apply(RDD.scala:902)

        at org.apache.spark.rdd.RDD$$anonfun$foreachPartition$1$$anonfun$apply$29.apply(RDD.scala:902)

        at org.apache.spark.SparkContext$$anonfun$runJob$5.apply(SparkContext.scala:1850)

        at org.apache.spark.SparkContext$$anonfun$runJob$5.apply(SparkContext.scala:1850)

        at org.apache.spark.scheduler.ResultTask.runTask(ResultTask.scala:66)

        at org.apache.spark.scheduler.Task.run(Task.scala:88)

        at org.apache.spark.executor.Executor$TaskRunner.run(Executor.scala:214)

        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)

        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)

        at java.lang.Thread.run(Thread.java:745)

4.        解决方法:

将对应ojdbc14.jar拷贝到当前节点的"spark.driver.extraClassPath"路径下即可。

5.        扩展:

可以尝试"spark.executor.extraClassPath"与"spark.driver.extraClassPath"是否设置的几种情况分别进行测试,以验证前面针对local时,仅"spark.driver.extraClassPath"有效的分析。

再次总结:只要设置"spark.driver.extraClassPath",并在该路径下放置了jar包即可。

 

3.2.3       自动上传jar包方式

当部署模式为CLIENT时,应用程序(对应Driver)会将childMainClass设置为传入的mainClass,然后启动JVM进程,对应代码如下所示:

if (deployMode == CLIENT) {

  childMainClass = args.mainClass

  if (isUserJar(args.primaryResource)) {

    childClasspath += args.primaryResource

  }

  if (args.jars != null) { childClasspath ++= args.jars.split(",") }

  if (args.childArgs != null) { childArgs ++= args.childArgs }

}

在client模式,直接启动应用程序的主类,同时,将主类的jar包和添加的jars包(如果在参数中设置的话)都添加到运行时的classpath中。即当前的driver的classpath会自动包含--jars 设置的jar包。

同时,driver通过启动的http服务上传该jar包,executor在执行时下载该jar包,同时放置到executor进程的classpath路径。

 

测试案例的构建:删除前面的环境变量或两个配置属性的设置,直接用--jars命令行选项指定所需的第三方jar包(即这里的驱动类jar包)即可。例如:

$SPARK_HOME/bin/spark-submit  --master local \

  --deploy-mode client \

  --driver-memory 2g \

  --driver-cores 1 \

  --total-executor-cores 2 \

  --executor-memory 4g \

  --conf "spark.ui.port"=4081 \

         --class com.mb.TestJarwithOracle \

         --jars "$SPARK_HOME/thirdlib/ojdbc14.jar" \

         /tmp/test/Spark15.jar    

此时会直--jars指定的jar包加入classpath路径,因此可以成功加载驱动类。

 

当将应用程序提交到集群中时,对应不同的部署模式(--deploy-mode)会有不同的情况,因此下面分别针对不同的部署模式进行分析。

3.3     master为集群的MasterURL+部署模式为Client时

Client部署模式时,在提交点启动应用程序,因此对应driver端也在提交节点。此时,"spark.driver.extraClassPath"路径对应提交节点的路径。Executor则由调度分配到其他执行节点,此时"spark.executor.extraClassPath"对应的路径应是针对实际分配执行executor的节点(不是提交节点!)。

3.3.1       配置属性方式

属于:集群中jar包部署+ 配置属性的方式

通过前面的测试与分析,应该可以知道配置属性的方式只是将所需jar包放入类加载时查找的路径中,而对应的jar包需要人为去部署。

对应在分布式集群环境下,相关的JVM进程可能在各个节点上启动,包括driver和executor进程。因此当某个节点启动某类进程时,需要保证已经手动在该节点上,对应于配置属性所设置的路径下,已经存在或部署了所需的jar包。

进一步地,通常由资源调度器负责分配节点,运行进程,因此为了保证分配的节点上的进程能成功加载所需类,应该在集群的所有节点上部署所需jar包。

优点:一次部署多次使用

缺点:jar包冲突

 

3.3.1.1    测试1

1.        启动命令:

         $SPARK_HOME/bin/spark-submit  --master spark://masternode:7078 \

           --conf "spark.executor.extraClassPath"="$SPARK_HOME/thirdlib/ojdbc14.jar" \

           --conf "spark.driver.extraClassPath"="$SPARK_HOME/thirdlib/ojdbc14.jar"   \

           --conf "spark.ui.port"=4071 \

           --class com.mb.TestJarwithOracle \

           /tmp/test/SparkTest.jar      

2.        测试说明:

a)        通过--conf命令行选项,设置Driver与Executor端的classpath配置属性。

b)       在Driver端的classpath路径下放置所需的jar包。

c)        在Executor端的classpath路径下删除所需的jar包。

补充:这里放置或删除jar包,可以简单通过修改对应配置属性的路径来模拟。

3.        测试结果:

a)        Driver端与Oracle的操作:由于设置了classpath路径,同时该路径下放置了所需的jar包,因此操作成功,直接查看driver的终端输出日志。

b)       Executor端与Oracle的操作:虽然设置了classpath路径,但该路径下没有放置所需的jar包,因此操作失败,错误信息如下所示(查看4040-默认端口-的executor页面,找到对应的stderr日志信息):

 

 

3.3.1.2    测试2

1.        启动命令:

         $SPARK_HOME/bin/spark-submit  --master spark://masternode:7078 \

           --conf "spark.executor.extraClassPath"="$SPARK_HOME/thirdlib/ojdbc14.jar" \

           --conf "spark.driver.extraClassPath"="$SPARK_HOME/thirdlib/ojdbc14.jar"   \

           --conf "spark.ui.port"=4071 \

           --class com.mb.TestJarwithOracle \

           /tmp/test/SparkTest.jar      

2.        测试说明:

a)        通过--conf命令行选项,设置Driver与Executor端的classpath配置属性。

b)       在Executor端的classpath路径下放置所需的jar包。

c)        在Driver端的classpath路径下删除所需的jar包。

3.        测试结果:

a)        Executor端与Oracle的操作:由于设置了classpath路径,同时该路径下放置了所需的jar包,因此操作成功。

b)       Driver端与Oracle的操作:虽然设置了classpath路径,但该路径下没有放置所需的jar包,因此操作失败,直接查看终端的错误信息,如下所示:

java.lang.ClassNotFoundException: oracle.jdbc.driver.OracleDriver

        at java.net.URLClassLoader$1.run(URLClassLoader.java:366)

        at java.net.URLClassLoader$1.run(URLClassLoader.java:355)

        at java.security.AccessController.doPrivileged(Native Method)

        at java.net.URLClassLoader.findClass(URLClassLoader.java:354)

        at java.lang.ClassLoader.loadClass(ClassLoader.java:425)

        at java.lang.ClassLoader.loadClass(ClassLoader.java:358)

        at java.lang.Class.forName0(Native Method)

        at java.lang.Class.forName(Class.java:191)

        at com.mb.TestJarwithOracle$.deleteRecodes(TestJarwithOracle.scala:38)

        at com.mb.TestJarwithOracle$.main(TestJarwithOracle.scala:27)

        at com.mb.TestJarwithOracle.main(TestJarwithOracle.scala)

        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)

        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

        at java.lang.reflect.Method.invoke(Method.java:606)

        at org.apache.spark.deploy.SparkSubmit$.org$apache$spark$deploy$SparkSubmit$$runMain(SparkSubmit.scala:674)

        at org.apache.spark.deploy.SparkSubmit$.doRunMain$1(SparkSubmit.scala:180)

        at org.apache.spark.deploy.SparkSubmit$.submit(SparkSubmit.scala:205)

        at org.apache.spark.deploy.SparkSubmit$.main(SparkSubmit.scala:120)

        at org.apache.spark.deploy.SparkSubmit.main(SparkSubmit.scala)

补充说明:Driver端捕捉了异常,因此Executor可以继续执行。

3.3.1.3    扩展1

在classpath对应的这两个配置属性中,使用不同路径的结果是不同的,比如前面测试案例中的配置属性对应路径修改为hdfs文件系统路径时,driver端的驱动类加载会抛出异常,命令如下:

$SPARK_HOME/bin/spark-submit  --master spark://nodemaster:7078 \

  --deploy-mode client \

  --driver-memory 2g \

  --driver-cores 1 \

  --total-executor-cores 2 \

  --executor-memory 4g \

  --conf "spark.ui.port"=4081 \

  --conf "spark.executor.extraClassPath"="hdfs://nodemaster:8020/tmp/sptest/ojdbc14.jar" \

  --conf "spark.driver.extraClassPath"="hdfs://nodemaster:8020/tmp/sptest/ojdbc14.jar"   \

         --class com.mb.TestJarwithOracle \

         /tmp/test/Spark15.jar    

简单理解:

1.       Driver端classpath相关的配置:在启动应用程序(Driver)时作为JVM的Options使用,此时只能识别本地路径,使用hdfs文件系统路径的话,无法识别,因此类加载会失败。

2.       Executor端classpath相关的配置:会根据指定的路径去下载jar包,hdfs等文件系统以及被封装,因此可以下载到本地——对应默认在work路径的app目录中,然后添加到仅的classpath路径下,因此可以识别hdfs等文件系统的路径。

 

3.3.1.4    扩展2

由于之前有人提过几次类似的问题, 再此顺便给出简单说明。

异常日志如下所示:

16/04/26 11:35:58 ERROR util.SparkUncaughtExceptionHandler: Uncaught exception in thread Thread[appclient-registration-retry-thread,5,main]

java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@6946dc9c rejected from java.util.concurrent.ThreadPoolExecutor@2b3dbbc3[Running, pool size = 1, active threads = 1, queued tasks = 0, completed tasks = 0]

        at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2048)

        at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:821)

        at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1372)

        at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:110)

        at org.apache.spark.deploy.client.AppClient$ClientEndpoint$$anonfun$tryRegisterAllMasters$1.apply(AppClient.scala:96)

        at org.apache.spark.deploy.client.AppClient$ClientEndpoint$$anonfun$tryRegisterAllMasters$1.apply(AppClient.scala:95)

        at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244)

        at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244)

        at scala.collection.IndexedSeqOptimized$class.foreach(IndexedSeqOptimized.scala:33)

        at scala.collection.mutable.ArrayOps$ofRef.foreach(ArrayOps.scala:108)

        at scala.collection.TraversableLike$class.map(TraversableLike.scala:244)

        at scala.collection.mutable.ArrayOps$ofRef.map(ArrayOps.scala:108)

        at org.apache.spark.deploy.client.AppClient$ClientEndpoint.tryRegisterAllMasters(AppClient.scala:95)

        at org.apache.spark.deploy.client.AppClient$ClientEndpoint.org$apache$spark$deploy$client$AppClient$ClientEndpoint$$registerWithMaster(AppClient.scala:121)

        at org.apache.spark.deploy.client.AppClient$ClientEndpoint$$anon$2$$anonfun$run$1.apply$mcV$sp(AppClient.scala:132)

        at org.apache.spark.util.Utils$.tryOrExit(Utils.scala:1119)

        at org.apache.spark.deploy.client.AppClient$ClientEndpoint$$anon$2.run(AppClient.scala:124)

        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)

        at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:304)

        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:178)

        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)

        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)

        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)

        at java.lang.Thread.run(Thread.java:745)

16/04/26 11:35:58 INFO storage.DiskBlockManager: Shutdown hook called

16/04/26 11:35:58 INFO util.ShutdownHookManager: Shutdown hook called

仅根据异常无法判断具体错误信息,需要跟踪其堆栈信息。根据堆栈信息,可以知道是AppClient(代表应用程序客户端)中的ClientEndpoint(RPC通讯终端)尝试注册到Master时被拒绝 ——此时,检查提交应用程序时使用的Master URL是否正确即可。

经测试验证,当使用错误的Master URL时,会抛出以上异常信息。

 

3.3.2       自动上传jar包方式

当部署模式为CLIENT时,应用程序(对应Driver)会将childMainClass设置为传入的mainClass,然后启动JVM进程,对应代码如下所示:

if (deployMode == CLIENT) {

  childMainClass = args.mainClass

  if (isUserJar(args.primaryResource)) {

    childClasspath += args.primaryResource

  }

  if (args.jars != null) { childClasspath ++= args.jars.split(",") }

  if (args.childArgs != null) { childArgs ++= args.childArgs }

}

在client模式,直接启动应用程序的主类,同时,将主类的jar包和添加的jars包(如果在参数中设置的话)都添加到运行时的classpath中。即当前的driver的classpath会自动包含--jars 设置的jar包。

同时,driver通过启动的http 服务上传该jar包,executor在执行时下载该jar包,同时放置到executor进程的classpath路径。

因此,--jars相当于:

1.      通过"spark.driver.extraClassPath"配置driver 端。

2.      通过"spark.executor.extraClassPath"配置executor端,同时将指定的jar包上传到http 服务,并下载到executor端的该配置路径下。

 

3.4     master为集群的MasterURL+部署模式为Cluster时

CLUSTER部署模式时,Driver在某个节点提交,但却是在集群调度分配的节点上运行。

此时,可以将Driver看成是特殊的Executor,同样由分配的节点运行JVM进程,但对应进程的classpath配置信息(补充说明下,看配置属性的extra名,应该可以知道是附加的,或新增的classpath内容,而不是全部)由各自对应的配置属性进行设置。

CLUSTER部署模式在Drive分配到节点,并在节点上启动,相关属性配置及其作用等和CLIENT部署模式基本一致。这里仅基于配置属性方式针对Driver进行解析。

 

3.4.1       应用程序提交方式

下面给出两种形式的提交命令:

1.      基于REST服务提交方式

$SPARK_HOME/bin/spark-submit  --master spark://nodemaster:6066 \

  --deploy-mode cluster \

  --driver-memory 2g \

  --driver-cores 1 \

  --total-executor-cores 2 \

  --executor-memory 4g \

  --conf "spark.ui.port"=4081 \

  --conf "spark.executor.extraClassPath"="hdfs://nodemaster:8020/tmp/sptest/ojdbc14.jar" \

  --conf "spark.driver.extraClassPath"="hdfs://nodemaster:8020/tmp/sptest/ojdbc14.jar"   \

         --class com.mb.TestJarwithOracle \

         hdfs://nodemaster:8020/tmp/sptest/Spark15.jar

该方式采用REST服务作为masterurl提交应用程序。对应的值参考8080(默认)监控界面,如下所示:

 1.5.2 Spark Master at spark://nodemaster:7078

·        URL: spark://nodemaster:7078

·        RESTURL: spark://nodemaster:6066 (cluster mode)

·        AliveWorkers: 7

·        Coresin use: 84 Total, 0 Used

·        Memoryin use: 420.0 GB Total, 0.0 B Used

·        Applications: 0 Running, 106 Completed

·        Drivers: 0 Running, 11 Completed

·        Status: ALIVE

日志:

Running Spark using the REST application submission protocol.

16/04/26 13:15:02 INFO rest.RestSubmissionClient: Submitting a request to launch an application in spark://nodemaster:7078.

16/04/26 13:15:03 WARN rest.RestSubmissionClient: Unable to connect to server spark://nodemaster:7078.

Warning: Master endpoint spark://nodemaster:7078 was not a REST server. Falling back to legacy submission gateway instead.

16/04/26 13:15:04 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable

当REST 服务方式提交尝试失败后,会退回到传统方式进行提交。

2.      传统提交方式

$SPARK_HOME/bin/spark-submit  --master spark://nodemaster:7078 \

  --deploy-mode cluster \

  --driver-memory 2g \

  --driver-cores 1 \

  --total-executor-cores 2 \

  --executor-memory 4g \

  --conf "spark.ui.port"=4081 \

  --conf "spark.executor.extraClassPath"="hdfs://nodemaster:8020/tmp/sptest/ojdbc14.jar" \

  --conf "spark.driver.extraClassPath"="hdfs://nodemaster:8020/tmp/sptest/ojdbc14.jar"   \

         --class com.mb.TestJarwithOracle \

         hdfs://nodemaster:8020/tmp/sptest/Spark15.jar

日志:

Running Spark using the REST application submission protocol.

16/04/26 13:20:58 INFO rest.RestSubmissionClient: Submitting a request to launch an application in spark://nodemaster:6066.

16/04/26 13:20:58 INFO rest.RestSubmissionClient: Submission successfully created as driver-20160426132058-0010. Polling submission state...

16/04/26 13:20:58 INFO rest.RestSubmissionClient: Submitting a request for the status of submission driver-20160426132058-0010 in spark://nodemaster:6066.

16/04/26 13:20:58 INFO rest.RestSubmissionClient: State of driver driver-20160426132058-0010 is now RUNNING.

16/04/26 13:20:58 INFO rest.RestSubmissionClient: Driver is running on worker worker-20160418110627-192.168.149.95-45661 at 192.168.149.95:45661.

16/04/26 13:20:58 INFO rest.RestSubmissionClient: Server responded with CreateSubmissionResponse:

{

  "action" : "CreateSubmissionResponse",

  "message" : "Driver successfully submitted as driver-20160426132058-0010",

  "serverSparkVersion" : "1.5.2",

  "submissionId" : "driver-20160426132058-0010",

  "success" : true

}

 

3.4.2       提交应用之后Driver的分析

两种方式都可以成功提交应用程序,对应在界面会分别增加一个Driver,同时Driver启动后,会和之前Client部署模式的流程一样,提交一个Application(这里的概念对应Executor),对应界面(8080默认端口界面-最下面)有:

两个driver提交的应用如下(8080默认端口界面):

对应下面的Application,配置及其影响和前面是一样的,只是当前的Driver在分配的节点上运行(所有相关路径等概念都改为基于该执行节点)。因此下面仅分析Driver的相关内容。

信息获取相关操作:

1.       点击Driver行所在的Worker,跳转到该Worker监控页面,到最下面,查找与Driver的SubmissionID相同的Driver信息,界面如下所示:

2.       点击对应的stderr日志信息,可以看到Driver的启动命令及其执行日志

3.       查看Driver的启动命令

a)        对应传统提交方式的日志如下所示:

Launch Command: "/usr/java/jdk1.7.0_71/bin/java" "-cp" "hdfs://nodemaster:8020/tmp/sptest/ojdbc14.jar:$SPARK_HOME/sbin/../conf/:$SPARK_HOME/lib/spark-assembly-1.5.2-hadoop2.6.0.jar:$SPARK_HOME/lib/datanucleus-api-jdo-3.2.6.jar:$SPARK_HOME/lib/datanucleus-core-3.2.10.jar:$SPARK_HOME/lib/datanucleus-rdbms-3.2.9.jar:/etc/hadoop/conf/" "-Xms2048M" "-Xmx2048M" "-Dspark.deploy.defaultCores=4" "-Dspark.eventLog.enabled=true" "-Dakka.loglevel=WARNING" "-Dspark.history.fs.cleaner.maxAge=7d" "-Dspark.submit.deployMode=cluster" "-Dspark.executor.memory=4g" "-Dspark.executor.extraClassPath=hdfs://nodemaster:8020/tmp/sptest/ojdbc14.jar" "-Dspark.executor.extraJavaOptions=-XX:+PrintGCDetails" "-Dspark.jars=hdfs://nodemaster:8020/tmp/sptest/Spark15.jar" "-Dspark.history.fs.cleaner.enabled=true" "-Dspark.master=spark://nodemaster:7078" "-Dspark.driver.supervise=false" "-Dspark.driver.extraClassPath=hdfs://nodemaster:8020/tmp/sptest/ojdbc14.jar" "-Dspark.app.name=com.mb.TestJarwithOracle" "-Dspark.history.fs.logDirectory=hdfs://nodemaster:8020/user/hdfs/sparklogs" "-Dspark.driver.memory=2g" "-Dspark.cores.max=2" "-Dspark.rpc.askTimeout=10" "-Dspark.eventLog.dir=hdfs://nodemaster:8020/user/hdfs/sparklogs" "-Dspark.ui.port=4081" "-Dspark.history.fs.cleaner.interval=1d" "-Dspark.driver.cores=1" "-XX:MaxPermSize=256m" "org.apache.spark.deploy.worker.DriverWrapper" "akka.tcp://[email protected]:45661/user/Worker" "$SPARK_HOME/work/driver-20160426131505-0009/Spark15.jar" "com.mb.TestJarwithOracle"

 

b)       对应REST提交方式的日志如下所示:

Launch Command: "/usr/java/jdk1.7.0_71/bin/java" "-cp" "hdfs://nodemaster:8020/tmp/sptest/ojdbc14.jar:$SPARK_HOME/sbin/../conf/:$SPARK_HOME/lib/spark-assembly-1.5.2-hadoop2.6.0.jar:$SPARK_HOME/lib/datanucleus-api-jdo-3.2.6.jar:$SPARK_HOME/lib/datanucleus-core-3.2.10.jar:$SPARK_HOME/lib/datanucleus-rdbms-3.2.9.jar" "-Xms2048M" "-Xmx2048M" "-Dspark.deploy.defaultCores=4" "-Dspark.eventLog.enabled=true" "-Dspark.history.fs.cleaner.maxAge=7d" "-Dspark.submit.deployMode=cluster" "-Dspark.executor.memory=4g" "-Dspark.executor.extraClassPath=hdfs://nodemaster:8020/tmp/sptest/ojdbc14.jar" "-Dspark.executor.extraJavaOptions=-XX:+PrintGCDetails" "-Dspark.jars=hdfs://nodemaster:8020/tmp/sptest/Spark15.jar" "-Dspark.history.fs.cleaner.enabled=true" "-Dspark.master=spark://nodemaster:7078" "-Dspark.driver.supervise=false" "-Dspark.driver.extraClassPath=hdfs://nodemaster:8020/tmp/sptest/ojdbc14.jar" "-Dspark.app.name=com.mb.TestJarwithOracle" "-Dspark.history.fs.logDirectory=hdfs://nodemaster:8020/user/hdfs/sparklogs" "-Dspark.driver.memory=2g" "-Dspark.cores.max=2" "-Dspark.eventLog.dir=hdfs://nodemaster:8020/user/hdfs/sparklogs" "-Dspark.ui.port=4081" "-Dspark.history.fs.cleaner.interval=1d" "-Dspark.driver.cores=1" "-XX:MaxPermSize=256m" "org.apache.spark.deploy.worker.DriverWrapper" "akka.tcp://[email protected]:45661/user/Worker" "$SPARK_HOME/work/driver-20160426132058-0010/Spark15.jar" "com.mb.TestJarwithOracle"

 

(暂时不考虑两者的差异,仅关注Driver进程相关的内容。)

下面对其中比较重要的几个部分进行分析:

1.       其中,启动的JVM进程主类为"org.apache.spark.deploy.worker.DriverWrapper",在"-cp"后加入了hdfs://nodemaster:8020/tmp/sptest/ojdbc14.jar,这里对应的是提交命令中的--conf"spark.driver.extraClassPath"配置属性(可通过修改设置的路径进行验证)。

在该启动命令(提交命令的配置参数)中,ojdbc14.jar路径为hdfs,因此无法识别,在driver部分的逻辑代码执行时会抛出异常。对应日志在该LaunchCommand:命令后面。因此"spark.driver.extraClassPath"配置属性中设置的路径应该对应当前节点(由于是调度分配的,对应就意味着应该是在集群中各个节点都进行部署)的路径,并且路径下有所需jar包。

2.       对应的spark.driver.extraClassPathspark.executor.extraClassPath等会继续作为Driver的参数传入(和Client部署模式下直接执行Driver一样)

3.       "-Dspark.jars=hdfs://nodemaster:8020/tmp/sptest/Spark15.jar"对应提交时的主资源。

4.       "$SPARK_HOME/work/driver-20160426132058-0010/Spark15.jar":work是默认的工作目录,driver-20160426132058-0010是当前Driver对应的Submission ID,在该目录中会下载Driver执行所需的Jar包,这里对应主资源Spark15.jar。

此时,需要注意的是,当CLUSTER部署模式时,如果使用的主资源是本地路径,如以下命令:

$SPARK_HOME/bin/spark-submit  --master spark://nodemaster:6066 \

  --deploy-mode cluster \

  --driver-memory 2g \

  --driver-cores 1 \

  --total-executor-cores 2 \

  --executor-memory 4g \

  --conf "spark.ui.port"=4081 \

  --conf "spark.executor.extraClassPath"="hdfs://nodemaster:8020/tmp/sptest/ojdbc14.jar" \

  --conf "spark.driver.extraClassPath"="$SPARK_HOME/thirdlib/ojdbc14.jar"   \

         --class com.mb.TestJarwithOracle \

         /tmp/test/Spark15.jar

其中红色部分对应为主资源jar包,采用本地文件系统的路径。执行时,Driver端输出的错误信息如下所示(对应界面的Driver状态为Error):

java.io.FileNotFoundException: /tmp/test/Spark15.jar (No such file or directory)

        java.io.FileInputStream.open(Native Method)

        java.io.FileInputStream.(FileInputStream.java:146)

        org.spark-project.guava.io.Files$FileByteSource.openStream(Files.java:124)

        org.spark-project.guava.io.Files$FileByteSource.openStream(Files.java:114)

        org.spark-project.guava.io.ByteSource.copyTo(ByteSource.java:202)

        org.spark-project.guava.io.Files.copy(Files.java:436)

        org.apache.spark.util.Utils$.org$apache$spark$util$Utils$$copyRecursive(Utils.scala:514)

        org.apache.spark.util.Utils$.copyFile(Utils.scala:485)

        org.apache.spark.util.Utils$.doFetchFile(Utils.scala:562)

        org.apache.spark.util.Utils$.fetchFile(Utils.scala:369)

        org.apache.spark.deploy.worker.DriverRunner.org$apache$spark$deploy$worker$DriverRunner$$downloadUserJar(DriverRunner.scala:150)

        org.apache.spark.deploy.worker.DriverRunner$$anon$1.run(DriverRunner.scala:79)

在DriverRunner启动后,调用downloadUserJar,下载所需jar包,但此时使用本地文件系统的路径,对应的就需要在Driver当前执行节点上的该路径下存在该jar包(当前未部署),因此报异常:java.io.FileNotFoundException:/tmp/test/Spark15.jar。

因此,对应在CLUSTER部署模式时,需要注意提交应用程序对应的主资源的配置:

1.       将主资源类似于其他的第三方jar包(如Oracle驱动类库的jar包)部署到集群中;

2.       使用HDFS这类文件系统,可以下载到本地工作目录。

4     总结

通过配置classpath,为JVM加载类时提供搜索路径。

在分布式计算集群中,需要注意JVM进程是在哪台节点上启动,对应节点上的classpath下是否部署了所需的jar包(针对jar包以本地路径的形式,即与具体节点相关的路径)。

因此总结起来有以下两点:

1.       是否为对应JVM进程指定了classpath;

2.       在各个进程的classpath路径下是否放置了所需的jar包。放置的方式可以有两种:

a)        一种是Spark框架提供的自动放置到classpath的方式;

b)       一种手动在集群中部署的方式;

这里指的路径,表示的是该JVM进程能够读取的路径,比如本地文件系统路径、hdfs路径,其中本地文件系统路径是针对本节点的,这点在分布式集群中尤其要注意。


 [h1]修改为local

 [h2]日志对应上面的修改

你可能感兴趣的:(Spark)