spark hive類總是優(yōu)先記載應(yīng)用里面的jar包,跟spark.{driver/executor}.userClassPathFirst 無關(guān)

背景

最近在弄spark on k8s的時候鹉胖,要集成同事的一些功能所宰,其實(shí)這并沒有什么绒尊,但是里面涉及到了hive的類問題(具體指這個org.apache.hadoop.hive.包下的類)。之后發(fā)現(xiàn)hive類總是優(yōu)先加載應(yīng)用jar包里的類仔粥,而忽略掉spark自帶的系統(tǒng)jars包婴谱,這給我?guī)Я肆撕艽蟮睦_蟹但,大約花了一兩周的時間,終于把這個問題排查清楚了谭羔。

問題分析

直接分析:
我們知道在spark提交的時候华糖,會獲取URLClassLoader去加載類,如下:

private def runMain(args: SparkSubmitArguments, uninitLog: Boolean): Unit = {
    val (childArgs, childClasspath, sparkConf, childMainClass) = prepareSubmitEnvironment(args)
    // Let the main class re-initialize the logging system once it starts.
    if (uninitLog) {
      Logging.uninitialize()
    }

    if (args.verbose) {
      logInfo(s"Main class:\n$childMainClass")
      logInfo(s"Arguments:\n${childArgs.mkString("\n")}")
      // sysProps may contain sensitive information, so redact before printing
      logInfo(s"Spark config:\n${Utils.redact(sparkConf.getAll.toMap).mkString("\n")}")
      logInfo(s"Classpath elements:\n${childClasspath.mkString("\n")}")
      logInfo("\n")
    }
    val loader = getSubmitClassLoader(sparkConf)
    for (jar <- childClasspath) {
      addJarToClasspath(jar, loader)
    }

    var mainClass: Class[_] = null

    try {
      mainClass = Utils.classForName(childMainClass)
    } catch {
      ....

這里的getSubmitClassLoader方法就是獲得URLClassloader:

private def getSubmitClassLoader(sparkConf: SparkConf): MutableURLClassLoader = {
    val loader =
      if (sparkConf.get(DRIVER_USER_CLASS_PATH_FIRST)) {
        new ChildFirstURLClassLoader(new Array[URL](0),
          Thread.currentThread.getContextClassLoader)
      } else {
        new MutableURLClassLoader(new Array[URL](0),
          Thread.currentThread.getContextClassLoader)
      }
    Thread.currentThread.setContextClassLoader(loader)
    loader
  }

區(qū)別就是ChildFirstURLClassLoader自定義了類加載的順序瘟裸,也就是會優(yōu)先加載應(yīng)用jar包里的順序缅阳,可是我們的應(yīng)用的并沒有設(shè)置spark.{driver/executor}.userClassPathFirst,所以該hive類是跟這個加載器無關(guān)的。
就在百思不得其解的時候景描,突然想到了spark 對于hive metastore的兼容性隨筆--通過classloader實(shí)現(xiàn),這里的實(shí)現(xiàn)十办,這里就不得不分析一下IsolatedClientLoader這個類的細(xì)節(jié):

 private[hive] val classLoader: MutableURLClassLoader = {
    val isolatedClassLoader =
      if (isolationOn) {
        if (allJars.isEmpty) {
          // See HiveUtils; this is the Java 9+ + builtin mode scenario
          baseClassLoader
        } else {
          val rootClassLoader: ClassLoader =
            if (SystemUtils.JAVA_VERSION.split("\\.")(1).toInt>=9) {
              // In Java 9, the boot classloader can see few JDK classes. The intended parent
              // classloader for delegation is now the platform classloader.
              // See http://java9.wtf/class-loading/
              val platformCL =
              classOf[ClassLoader].getMethod("getPlatformClassLoader").
                invoke(null).asInstanceOf[ClassLoader]
              // Check to make sure that the root classloader does not know about Hive.
              assert(Try(platformCL.loadClass("org.apache.hadoop.hive.conf.HiveConf")).isFailure)
              platformCL
            } else {
              // The boot classloader is represented by null (the instance itself isn't accessible)
              // and before Java 9 can see all JDK classes
              null
            }
          new URLClassLoader(allJars, rootClassLoader) {
            override def loadClass(name: String, resolve: Boolean): Class[_] = {
              val loaded = findLoadedClass(name)
              if (loaded == null) doLoadClass(name, resolve) else loaded
            }
            def doLoadClass(name: String, resolve: Boolean): Class[_] = {
              val classFileName = name.replaceAll("\\.", "/") + ".class"
              if (isBarrierClass(name)) {
                // For barrier classes, we construct a new copy of the class.
                val bytes = IOUtils.toByteArray(baseClassLoader.getResourceAsStream(classFileName))
                logDebug(s"custom defining: $name - ${util.Arrays.hashCode(bytes)}")
                defineClass(name, bytes, 0, bytes.length)
              } else if (!isSharedClass(name)) {
                logDebug(s"hive class: $name - ${getResource(classToPath(name))}")
                super.loadClass(name, resolve)
              } else {
                // For shared classes, we delegate to baseClassLoader, but fall back in case the
                // class is not found.
                logDebug(s"shared class: $name")
                try {
                  baseClassLoader.loadClass(name)
                } catch {
                  case _: ClassNotFoundException =>
                    super.loadClass(name, resolve)
                }
              }
            }
          }
        }
      } else {
        baseClassLoader
      }

如果是在JDK8的情況下rootClassLoader是為null的,這就導(dǎo)致了在加載hive相關(guān)的類的時候超棺,super.loadClass方法就會直接執(zhí)行URLClassLoader的findClass方法向族,進(jìn)而從URL(也就是通過addURL方法加載進(jìn)來的jar)查著相關(guān)的類
而在spark中,最終的任務(wù)提交是通過SparkSubmit的runMain方法來提交的棠绘,代碼如第一塊代碼:

 val loader = getSubmitClassLoader(sparkConf)
    for (jar <- childClasspath) {
      addJarToClasspath(jar, loader)
    }

注意childClasspath這個是怎么來著件相,我們在提交任務(wù)的時候,是可以看到Classpath elements輸出的氧苍,也就是會包括--jars指定的jar包和因公的jar包夜矗,所以在加載hive相關(guān)的類的時候,就會優(yōu)先從childClassPath中加載對應(yīng)的class让虐,這是通過IolatedClassLoader實(shí)現(xiàn)紊撕。

解決方法

但是如果應(yīng)用中用到的hive相關(guān)的類和系統(tǒng)的類不一致的話,該怎么解決赡突,可以用maven shade插件進(jìn)行重命名对扶,使應(yīng)用的jar包使用重名以后的類,而不會影響其他的類使用系統(tǒng)自帶的hive相關(guān)的類

結(jié)論

hive相關(guān)的class(在org.apache.hadoop.hive包下)惭缰,跟spark.{driver/executor}.userClassPathFirst無關(guān),跟IolatedClassLoader實(shí)現(xiàn)有關(guān)浪南。具體想看哪些hive類是從哪個jar包加載進(jìn)來的,可以開啟debug日志

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末漱受,一起剝皮案震驚了整個濱河市络凿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌昂羡,老刑警劉巖絮记,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異紧憾,居然都是意外死亡到千,警方通過查閱死者的電腦和手機(jī)昌渤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進(jìn)店門赴穗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事般眉×苏裕” “怎么了?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵甸赃,是天一觀的道長柿汛。 經(jīng)常有香客問我,道長埠对,這世上最難降的妖魔是什么络断? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮项玛,結(jié)果婚禮上貌笨,老公的妹妹穿的比我還像新娘。我一直安慰自己襟沮,他們只是感情好锥惋,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著开伏,像睡著了一般膀跌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上固灵,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天捅伤,我揣著相機(jī)與錄音,去河邊找鬼巫玻。 笑死暑认,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的大审。 我是一名探鬼主播蘸际,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼徒扶!你這毒婦竟也來了粮彤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤姜骡,失蹤者是張志新(化名)和其女友劉穎导坟,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體圈澈,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡惫周,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了康栈。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片递递。...
    茶點(diǎn)故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡喷橙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出登舞,到底是詐尸還是另有隱情贰逾,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布菠秒,位于F島的核電站疙剑,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏践叠。R本人自食惡果不足惜言缤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望禁灼。 院中可真熱鬧轧简,春花似錦、人聲如沸匾二。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽察藐。三九已至皮璧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間分飞,已是汗流浹背悴务。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留譬猫,地道東北人讯檐。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像染服,于是被迫代替她去往敵國和親别洪。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評論 2 345