基於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.<init>(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.<init>(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]日誌對應上面的修改

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章