1. 程式人生 > >spark on yarn作業執行的jar包快取優化

spark on yarn作業執行的jar包快取優化

原文地址

參考自下文

這幾天一直在追查spark on yarn的作業執行中的jar包分發,以及執行後的jar包刪除的問題。從一開始的毫無頭緒,到後來逐漸清晰,到後來通過hadoop的兩個很簡單的引數配置解決了問題。不得不說,雖然問題不大,對某些大牛們來說也真是小case,但是追查問題,定位問題到最終解決問題的過程,對我來說真是很不錯的體驗和學習過程。下面詳細描述一下遇到的問題,以及解決的過程,給後面的同學一點參考。
BTW,很多時候定位問題,查清楚問題的原因,比解決這個”問題“要更重要。

問題描述

Spark版本是1.0.2,執行在cdh5.1.0-hadoop2.3.0版本的yarn上面。在每次提交作業執行之後,在我的HDFS的$

{yarn.app.mapreduce.am.staging-dir}/${username}/.sparkStaging下面,就會產生兩個jar包,一個是spark-assembly-*.jar,另一個是提交執行的jar包。同時,在這個作業對應的executor的節點上,${yarn.nodemanager.local-dirs}目錄下,也會有這兩個jar包。而且不會被刪除。

光spark-assembly*.jar就有一百多M,每次執行完都會留下這麼多jar包,雖然不大,但可想而知,日積月累,可是個很麻煩的事情。所以,要搞清楚為啥會出現這個情況。

這個問題可以拆分成兩個sub-task,一個是HDFS上的jar包為啥不會自動刪除,一個是nodemanager節點上的jar包的自動刪除。下面分別排查和解決:

HDFS上的jar包快取

網上貌似很少有相關資料,就是找到了一個介紹如何配置spark.yarn.jar的文章,說是可以解決HDFS上的jar包快取的問題。照著配置了一下,不起作用,又看作者說,要spark1.1.0以上的版本,所以重新編譯了spark1.1.0,把叢集的spark1.0.2升級到1.1.0。重新配置spark.yarn.jar,發現問題解決了。HDFS上仍然在每次提交作業時都會被上傳兩個jar包,但是用完後會自動刪除。

配置方法

(1) 首先需要確保spark在1.1.0以上的版本。

(2) 在HDFS上建立一個公共lib庫,比如/system/spark-lib/,設定許可權為755。把spark-assembly-*.jar上傳到公共lib庫中。

(3) 在spark-env.sh中配置:

spark.yarn.jar                          hdfs://yarncluster/system/spark_lib/spark-assembly-1.1.0-hadoop2.3.0-cdh5.1.0.jar  
spark.yarn.preserve.staging.files       false
  • spark.yarn.jar配置成HDFS上的公共lib庫中的jar包。這個配置項會使提交job時,不是從本地上傳spark-assembly*.jar包,而是從HDFS的一個目錄複製到另一個目錄(不確定HDFS上的複製是怎麼操作的),總的來說節省了一點時間。(網上有的文章裡說,這裡的配置,會節省掉上傳jar包的步驟,其實是不對的,只是把從本地上傳的步驟改成了在HDFS上的複製操作。)
  • spark.yarn.preserve.staging.files: 這個配置項配置成false,表示在執行結束後,不保留staging
    files,也就是兩個jar包。然後HDFS上的.sparkStaging下的兩個jar包在作業執行完成後就會被刪除。如果配置成true,執行完後HDFS上的.sparkStaging下兩個jar包都會儲存下來。

然後再執行,發現HDFS上.sparkStaging目錄下不會再保留jar包。

問題定位

按道理來說,因為spark.yarn.preserve.staging.files預設是false,所以HDFS上的jar包是不會被保留的。但是在spark1.0.2中,卻沒有刪除。我看了下1.0.2的程式碼,刪除的機制是存在的:

//yarn/alpha/src/main/Scala/org/apache/spark/deploy/yarn/ApplicationMaster.scala 
/** 
   * Clean up the staging directory. 
   */  
  private def cleanupStagingDir() {  
    var stagingDirPath: Path = null  
    try {  
      val preserveFiles = sparkConf.get("spark.yarn.preserve.staging.files", "false").toBoolean  
      if (!preserveFiles) {  
        stagingDirPath = new Path(System.getenv("SPARK_YARN_STAGING_DIR"))  
        if (stagingDirPath == null) {  
          logError("Staging directory is null")  
          return  
        }  
        logInfo("Deleting staging directory " + stagingDirPath)  
        fs.delete(stagingDirPath, true)  
      }  
    } catch {  
      case ioe: IOException =>  
        logError("Failed to cleanup staging dir " + stagingDirPath, ioe)  
    }  
  }

按照這個邏輯,預設在AM關閉的時候,是會刪除HDFS上的jar包的。不過沒有正常刪除。推測這應該是一個1.0.2裡面的bug,而在1.1.0裡面已經修復。

nodemanager節點上的jar包快取

升級到1.1.0版本後,HDFS上的jar包問題就解決了。但是nodemanager節點上的jar包還是會保留。這個問題的定位很糾結,不過結果卻出乎意料的簡單。不說了,上結果吧。

配置方法

(1) 配置yarn-site.xml:

<property>
    <name>yarn.nodemanager.local-dirs</name>
    <value>local-dir1, local-dir2,local-dir3</value>
</property>
<property>
    <name>yarn.nodemanager.localizer.cache.target-size-mb</name>
    <value>1024</value>
</property>
<property>
    <name>yarn.nodemanager.localizer.cache.cleanup.interval-ms</name>
    <value>1800000</value>
</property>
  • yarn.nodemanager.local-dirs:
    這個目錄是nodemanager上的作業中間資料存放路徑。推薦配置多個盤上的多個路徑,從而分散作業執行中的磁碟IO壓力。
  • yarn.nodemanager.localizer.cache.target-size-mb:配置nodemanager上的快取目錄的最大限度。nodemanager上有一個deletion
    server服務,會定期檢測,如果yarn.nodemanager.local-dirs中配置的目錄大小(如果配置了多個,則計算多個目錄的總大小)是否超過了這裡設定的最大限度值。如果超過了,就刪除一些已經執行完的Container的快取資料。
  • yarn.nodemanager.localizer.cache.cleanup.interval-ms: deletion
    server多長時間做一次檢測,並且清除快取目錄直到目錄大小低於target-size-mb的配置。

因為spark提交作業後遺留在nodemanager上的jar包就在yarn.nodemanager.local-dirs下面,所以只要這裡配置合適的大小值。那麼nodemanager上的deletion server是會自動檢測並保證目錄總大小的。所以只要配置了這個量,我們就不需要再擔心nodemanager上的jar包快取問題了,交給yarn就好了!很簡單啊有木有,可就這麼個問題,居然花了我一個星期的時間去定位。

通過上面這三個量的配置,nodemanager會確保本地的快取資料總量在target-size-mb之下,也就是超過了的話,之前的spark的jar包就會被刪除。所以我們就不需要再擔心nodemanager節點上的spark jar包快取問題了。不過target-size-mb的預設值是10G,這個值當然可以根據你的實際情況進行調整。

問題定位

為什麼mapreduce提交的任務在執行完後,會自動刪除nodemanager上的所有中間檔案,而spark卻不會呢?

查看了下Hadoop的原始碼(相關程式碼就不貼出來了),MapReduce提交作業的時候,中間資料是以application file物件的方式被處理的,也就是在nodemanager上是存放在username/appcache/目錄下的,而按照yarn的處理機制,application file是專門存放中間資料的物件,所以MapReduce作業的所有中間資料在作業執行完後會被yarn刪除。

而spark on yarn的任務提交後,兩個jar包卻是以yarn的分散式快取(distributed cache)的方式存放和處理的。Distributed Cache是yarn提供給使用者用來分發和管理作業執行中用到的extra file的處理機制。spark沒有呼叫yarn的application file物件,卻提交成distributed cache型別,不知道為什麼。不過這就是jar包最終沒有被自動刪除的原因。

下面是MapReduce任務提交後nm上分發中間檔案的日誌:

這裡寫圖片描述

可以看到中間檔案都被下載到appcache的目錄下。

再看看spark任務提交後nm上分發jar包的日誌:
這裡寫圖片描述

很明顯jar包被存放在filecache目錄下。也就是它是作為distributed cache的方式分發到nm上的。不知道為什麼spark採用這樣的設計方式。file cache在作業結束後,是不會主動被刪除的。

就是這樣了,這個問題其實根本也算不上一個問題。也就是一個深入學習spark on yarn的架構和解惑的過程。

更新:

spark採用file cache而不是application cache的原因,是因為spark在每個NM上啟動的是一個executor,每個task作為executor裡的一個執行緒在執行。而只需要在executor啟動時,在這個節點上用file cache機制分發一次jar包,然後在每個task執行時,能共享到通過file cache傳過來的jar包。所以採用file cache機制的原因,是要在task之間共享jar包。