天天看點

spark-submit報錯:Exception in thread "main" java.sql.SQLException:No suitable driver

版權聲明:本文由董可倫首發于https://dongkelun.com,非商業轉載請注明作者及原創出處。商業轉載請聯系作者本人。 https://blog.csdn.net/dkl12/article/details/80256619

我的原創位址:

https://dongkelun.com/2018/05/06/sparkSubmitException/

前言

最近寫了一個用spark連接配接oracle,然後将mysql所有的表儲存到hive中的程式,在本地eclipse裡運作沒有問題,想在叢集上跑一下,看看在叢集上性能如何,但是用spark-submit 送出程式時抛出一個異常Exception in thread “main” java.sql.SQLException: No suitable driver,一開始以為spark-submit送出時找不到oracle 驅動jar,折騰了半天才發現是代碼問題。

1、猜測是否是缺失oracle驅動

由于在本地沒有問題,是以不會想到是代碼問題,根據提示想到的是spark-submit找不到oracle驅動,因為maven或sbt倉庫裡沒有oracle驅動,在本地跑的時候,是将oracle驅動下載下傳到本地,然後在eclipse設定build path就可以了。

但是我在spark-submit 裡已經通過–jars 加載oracle驅動了:

spark-submit --class com.dkl.leanring.spark.sql.Oracle2HiveDemo --jars ojdbc5-11.2.0.3.jar spark-scala_2.11-1.0.jar           

開始以為自己用法不對,但是上網搜了一下,發現就是這麼用的~

然後嘗試用–driver-class-path、–driver-library-path等都沒成功。

2、sbt-assembly打包

網上查的sbt-assembly打包可以将所有依賴的jar包包括你寫的代碼全部打包在一起,于是嘗試了一下

首先在項目目錄中project/plugins.sbt添加

addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.5")
resolvers += Resolver.url("bintray-sbt-plugins", url("http://dl.bintray.com/sbt/sbt-plugin-releases"))(Resolver.ivyStylePatterns)           

其中0.14.5為版本号,需要與自己sbt對應上,否則報錯,可在

http://dl.bintray.com/sbt/sbt-plugin-releases/com.eed3si9n/sbt-assembly/

檢視版本

然後在項目對應目錄下執行sbt,然後輸入plugins,即可看到sbtassembly插件了,如下:

$ sbt
[info] Loading settings from plugins.sbt ...
[info] Loading global plugins from C:\Users\14123\.sbt\1.0\plugins
[info] Loading settings from assembly.sbt,plugins.sbt ...
[info] Loading project definition from D:\workspace\spark-scala\project
[info] Loading settings from spark-scala.sbt ...
[info] Set current project to spark-scala (in build file:/D:/workspace/spark-scala/)
[info] sbt server started at local:sbt-server-8b6c904d40b181717b3f
sbt:spark-scala> plugins
In file:/D:/workspace/spark-scala/
        sbt.plugins.IvyPlugin: enabled in spark-scala
        sbt.plugins.JvmPlugin: enabled in spark-scala
        sbt.plugins.CorePlugin: enabled in spark-scala
        sbt.plugins.JUnitXmlReportPlugin: enabled in spark-scala
        sbt.plugins.Giter8TemplatePlugin: enabled in spark-scala
        com.typesafe.sbteclipse.plugin.EclipsePlugin: enabled in spark-scala
        sbtassembly.AssemblyPlugin: enabled in spark-scala
sbt:spark-scala>
           

但是這樣執行sbt-assembly打包會報錯,需要解決jar包沖突(deduplicate)問題

在項目的bulid.sbt裡添加如下即可(隻是其中一種解決政策,可根據自己項目實際情況自己設定)

assemblyMergeStrategy in assembly := {
    case m if m.toLowerCase.endsWith("manifest.mf") => MergeStrategy.discard
    case m if m.startsWith("META-INF") => MergeStrategy.discard
    case PathList("javax", "servlet", xs @ _*) => MergeStrategy.first
    case PathList("org", "apache", xs @ _*) => MergeStrategy.first
    case PathList("org", "jboss", xs @ _*) => MergeStrategy.first
    case "about.html"  => MergeStrategy.rename
    case "reference.conf" => MergeStrategy.concat
    case _ => MergeStrategy.first
}           

其中不同舊版本和新版本sbt寫法不一樣,具體可上網看一下别人的部落格或者在官網檢視。

這樣就可以sbt-assembly進行打包了,發現這樣打的jar包确實很大,用sbt package打的jar包大小1.48MB,sbt-assembly打的jar包大小194MB,将spark-scala-assembly-1.0.jar上傳到伺服器,然後執行submit,發現還是報同樣的錯,檢視一下sbt-assembly日志,發現确實将oracle驅動加載上了~

3、真正原因

這樣就猜想不是缺少oracle驅動,于是上網查了好多,偶然發現可能是代碼問題,下面是我寫的從oracle取數的部分代碼

val allTablesDF = spark.read
  .format("jdbc")
  .option("url", "jdbc:oracle:thin:@192.168.44.128:1521:orcl")
  .option("dbtable", "(select table_name,owner from all_tables where  owner  in('BIGDATA'))a")
  .option("user", "bigdata")
  .option("password", "bigdata")
  .load()           

寫法和我之前寫的spark連接配接mysql的部落格裡的寫法是一樣的:

Spark Sql 連接配接mysql

這樣寫在eclipse運作是沒問題的,但是在spark-submit送出時是不行的,需要加上驅動資訊

.option("driver", "oracle.jdbc.driver.OracleDriver")           

重新打包,再運作,發現果然沒問題

4、總結

4.1

其實在用spark送出之前寫的spark連接配接mysql的程式也會報統一的錯(如果$SPARK_HOME/jars沒有mysql驅動),和oracle驅動不在sbt倉庫裡沒關系。

但是之前在spark-shell裡測試spark連接配接hive時已經将mysql驅動拷貝過去了,是以mysql沒有報錯

4.2

在代碼裡加上driver之後再送出如果沒有oracle驅動會報不同的錯

Exception in thread "main" java.lang.ClassNotFoundException: oracle.jdbc.driver.OracleDriver           

4.3

通過–jars指定jar和sbt assembly打包都可以,看自己習慣,但通過–jars需要注意先後順序

正确:

spark-submit --class com.dkl.leanring.spark.sql.Oracle2HiveDemo --jars ojdbc5-11.2.0.3.jar spark-scala_2.11-1.0.jar 
spark-submit --jars ojdbc5-11.2.0.3.jar --class com.dkl.leanring.spark.sql.Oracle2HiveDemo  spark-scala_2.11-1.0.jar            

錯誤:

spark-submit --class com.dkl.leanring.spark.sql.Oracle2HiveDemo  spark-scala_2.11-1.0.jar --jars ojdbc5-11.2.0.3.jar
spark-submit --jars ojdbc5-11.2.0.3.jar spark-scala_2.11-1.0.jar --class com.dkl.leanring.spark.sql.Oracle2HiveDemo 
...           

也就是通過sbt package和sbt assembly生成的項目jar包一定要放在最後面

4.4

通過–driver-class-path也可以實作加載額外的jar

spark-submit --class com.dkl.leanring.spark.sql.Oracle2HiveDemo --driver-class-path lib/*  spark-scala_2.11-1.0.jar           

4.5

将oracle驅動拷貝到$SPARK_HOME/jars,就可以不在代碼裡指定driver選項了,而且也不用通過–jars添加oracle驅動,一勞永逸.

cp ojdbc5-11.2.0.3.jar $SPARK_HOME/jars
spark-submit --class com.dkl.leanring.spark.sql.Oracle2HiveDemo  spark-scala_2.11-1.0.jar           

具體這樣設定可根據實際情況和偏好習慣使用。

附完整代碼(測試用)

比較簡單就不加注釋~

package com.dkl.leanring.spark.sql

import org.apache.spark.sql.SparkSession

object Oracle2HiveDemo {

  def main(args: Array[String]): Unit = {
    val spark = SparkSession
      .builder()
      .appName("Oracle2HiveDemo")
      .master("local")
      .enableHiveSupport()
      .getOrCreate()

    val allTablesDF = spark.read
      .format("jdbc")
      .option("url", "jdbc:oracle:thin:@192.168.44.128:1521:orcl")
      .option("dbtable", "(select table_name,owner from all_tables where  owner  in('BIGDATA'))a")
      .option("user", "bigdata")
      .option("password", "bigdata")
      .option("driver", "oracle.jdbc.driver.OracleDriver")
      .load()
    import spark.implicits._
    import spark.sql
    sql("use oracle_test")
    allTablesDF.rdd.collect().foreach(row => {
      val tableName: String = row(0).toString()
      val dataBase: String = row(1).toString()

      println(dataBase + "." + tableName)
      val df = spark.read
        .format("jdbc")
        .option("url", "jdbc:oracle:thin:@192.168.44.128:1521:orcl")
        .option("dbtable", dataBase + "." + tableName)
        .option("user", "bigdata")
        .option("password", "bigdata")
        .option("driver", "oracle.jdbc.driver.OracleDriver")
        .load()
      df.write.mode("overwrite").saveAsTable(tableName)

    })

    spark.stop

  }
}