Spark性能優化:資源調優篇

但凡是資源調優,就是通過軟件/程序的環境參數的配置調整,來達到程序運行的相對比較高效率的目的。

       比如在spark中配置spark.executor.nums/spark.executor.memory

/spark.driver.memory等等。

       在開發完Spark作業之後,就該爲作業配置合適的資源了。Spark的資源參數,基本都可以在spark-submit命令中作爲參數設置。很多Spark初學者,通常不知道該設置哪些必要的參數,以及如何設置這些參數,最後就只能胡亂設置,甚至壓根兒不設置。資源參數設置的不合理,可能會導致沒有充分利用集羣資源,作業運行會極其緩慢;或者設置的資源過大,隊列沒有足夠的資源來提供,進而導致各種異常。總之,無論是哪種情況,都會導致Spark作業的運行效率低下,甚至根本無法運行。因此我們必須對Spark作業的資源使用原理有一個清晰的認識,並知道在Spark作業運行過程中,有哪些資源參數是可以設置的,以及如何設置合適的參數值。

參考:http://spark.apache.org/docs/1.6.2/configuration.html

       運行設置

       spark.driver.extraJavaOptions:設置driver的相關於JVM的命令參數:比如:

-Xms:2G-Xmx:4G

       注意:當啓動spark程序爲client模式的時候,不能通過sparkConf來進行設置,因爲設置sparkConf的時候,Driver已經啓動了;可以通過--driver-java-options(在spark-submit中設置)。

一:資源參數調優

num-executors(必須設置)

- 參數說明:該參數用於設置Spark作業總共要用多少個Executor進程來執行。Driver在向YARN集羣管理器申請資源時,YARN集羣管理器會儘可能按照你的設置來在集羣的各個工作節點上,啓動相應數量的Executor進程。這個參數非常之重要,如果不設置的話,默認只會給你啓動少量的Executor進程,此時你的Spark作業的運行速度是非常慢的。

- 參數調優建議:每個Spark作業的運行一般設置50~100個左右的Executor進程比較合適,設置太少或太多的Executor進程都不好。設置的太少,無法充分利用集羣資源;設置的太多的話,大部分隊列可能無法給予充分的資源。

executor-memory

- 參數說明:該參數用於設置每個Executor進程的內存。Executor內存的大小,很多時候直接決定了Spark作業的性能,而且跟常見的JVMOOM異常,也有直接的關聯。

- 參數調優建議:每個Executor進程的內存設置4G~8G較爲合適。但是這只是一個參考值,具體的設置還是得根據不同部門的資源隊列來定。可以看看自己團隊的資源隊列的最大內存限制是多少,num-executors乘以executor-memory,就代表了你的Spark作業申請到的總內存量(也就是所有Executor進程的內存總和),這個量是不能超過隊列的最大內存量的。此外,如果你是跟團隊裏其他人共享這個資源隊列,那麼申請的總內存量最好不要超過資源隊列最大總內存的1/3~1/2,避免你自己的Spark作業佔用了隊列所有的資源,導致別的同學的作業無法運行。

executor-cores

·        參數說明:該參數用於設置每個Executor進程的CPU core數量。這個參數決定了每個Executor進程並行執行task線程的能力。因爲每個CPU core同一時間只能執行一個task線程,因此每個Executor進程的CPU core數量越多,越能夠快速地執行完分配給自己的所有task線程。

·        參數調優建議:Executor的CPU core數量設置爲2~4個較爲合適。同樣得根據不同部門的資源隊列來定,可以看看自己的資源隊列的最大CPU core限制是多少,再依據設置的Executor數量,來決定每個Executor進程可以分配到幾個CPU core。同樣建議,如果是跟他人共享這個隊列,那麼num-executors * executor-cores不要超過隊列總CPU core的1/3~1/2左右比較合適,也是避免影響其他同學的作業運行。 

driver-memory

·        參數說明:該參數用於設置Driver進程的內存。

·        參數調優建議:Driver的內存通常來說不設置,或者設置1G左右應該就夠了。唯一需要注意的一點是,如果需要使用collect算子將RDD的數據全部拉取到Driver上進行處理,那麼必須確保Driver的內存足夠大,否則會出現OOM內存溢出的問題。

spark.default.parallelism(設置默認並行度)

·        參數說明:該參數用於設置每個stage的默認task數量。這個參數極爲重要,如果不設置可能會直接影響你的Spark作業性能。

·        參數調優建議:Spark作業的默認task數量爲500~1000個較爲合適。很多同學常犯的一個錯誤就是不去設置這個參數,那麼此時就會導致Spark自己根據底層HDFS的block數量來設置task的數量,默認是一個HDFS block對應一個task。通常來說,Spark默認設置的數量是偏少的(比如就幾十個task),如果task數量偏少的話,就會導致你前面設置好的Executor的參數都前功盡棄。試想一下,無論你的Executor進程有多少個,內存和CPU有多大,但是task只有1個或者10個,那麼90%的Executor進程可能根本就沒有task執行,也就是白白浪費了資源!因此Spark官網建議的設置原則是,設置該參數爲num-executors * executor-cores的2~3倍較爲合適,比如Executor的總CPU core數量爲300個,那麼設置1000個task是可以的,此時可以充分地利用Spark集羣的資源。

spark.storage.memoryFraction(緩存比例)

executor內存默認分配(60%緩存rdd,20%shuffle拉取數據,20%rdd其他計算)

- 參數說明:該參數用於設置RDD持久化數據在Executor內存中能佔的比例,默認是0.6。也就是說,默認Executor60%的內存,可以用來保存持久化的RDD數據。根據你選擇的不同的持久化策略,如果內存不夠時,可能數據就不會持久化,或者數據會寫入磁盤。

- 參數調優建議:如果Spark作業中,有較多的RDD持久化操作,該參數的值可以適當提高一些,保證持久化的數據能夠容納在內存中。避免內存不夠緩存所有的數據,導致數據只能寫入磁盤中,降低了性能。但是如果Spark作業中的shuffle類操作比較多,而持久化操作比較少,那麼這個參數的值適當降低一些比較合適。此外,如果發現作業由於頻繁的gc導致運行緩慢(通過spark web ui可以觀察到作業的gc耗時),意味着task執行用戶代碼的內存不夠用,那麼同樣建議調低這個參數的值。

spark.shuffle.memoryFraction

- 參數說明:該參數用於設置shuffle過程中一個task拉取到上個stagetask的輸出後,進行聚合操作時能夠使用的Executor內存的比例,默認是0.2。也就是說,Executor默認只有20%的內存用來進行該操作。shuffle操作在進行聚合時,如果發現使用的內存超出了這個20%的限制,那麼多餘的數據就會溢寫到磁盤文件中去,此時就會極大地降低性能。

- 參數調優建議:如果Spark作業中的RDD持久化操作較少,shuffle操作較多時,建議降低持久化操作的內存佔比,提高shuffle操作的內存佔比比例,避免shuffle過程中數據過多時內存不夠用,必須溢寫到磁盤上,降低了性能。此外,如果發現作業由於頻繁的gc導致運行緩慢,意味着task執行用戶代碼的內存不夠用,那麼同樣建議調低這個參數的值。 
資源參數的調優,沒有一個固定的值,需要同學們根據自己的實際情況(包括Spark作業中的shuffle操作數量、RDD持久化操作數量以及spark web ui中顯示的作業gc情況),同時參考本篇文章中給出的原理以及調優建議,合理地設置上述參數。


資源參數參考示例


./bin/spark-submit\ 

      --master yarn \

      --deploy-mode cluster \ 

      --num-executors 128 \ 

      --executor-memory 27G \ 

      --executor-cores 8 \ 

      --driver-memory 8G \ 

      --conf spark.default.parallelism=1600\ 

     --conf spark.storage.memoryFraction=0.5 \ 

      --conf spark.shuffle.memoryFraction=0.3 \ 


二:Java虛擬機垃圾回收(GC)調優

概述:

       1、如果在持久化RDD的時候,持久化了大量的數據,那麼Java虛擬機的垃圾回收就可能成爲一個性能瓶頸。因爲Java虛擬機會定期進行垃圾回收,此時就會追蹤所有的java對象,並且在垃圾回收時,找到那些已經不在使用的對象,然後清理舊的對象,來給新的對象騰出內存空間。

 

       2、垃圾回收的性能開銷,是跟內存中的對象的數量,成正比的。所以,對於垃圾回收的性能問題,首先要做的就是,使用更高效的數據結構,比如array和string;其次就是在持久化rdd時,使用序列化的持久化級別,而且用Kryo序列化類庫,這樣,每個partition就只是一個對象——一個字節數組。

 

檢測(Java提供的內存檢測機制java/bin/):

       1、檢測垃圾回收機制的工具有很多,比如jstat,首先,GC監控方法根據訪問的接口不同,可以分成CUI 和GUI 兩大類。CUI GC監控方法使用一個獨立的叫做”jstat”的CUI應用,或者在啓動JVM的時候選擇JVM參數”verbosegc”。

  GUI GC監控由一個單獨的圖形化應用來完成,其中三個最常用的應用是”jconsole”, “jvisualvm” 和 “Visual GC”。

 

       2、在spark-submit腳本中,增加一個配置即可,--conf"spark.executor.extraJavaOptions=-verbose:gc -XX:+PrintGCDetails-XX:+PrintGCTimeStamps"。

 

       但是要記住,這裏雖然會打印出Java虛擬機的垃圾回收的相關信息,但是是輸出到了worker上的日誌中,而不是driver的日誌中。

 

       3、我們完全可以通過SparkUI(4040,4041或者4042端口)來觀察每個stage的垃圾回收的情況。

 

優化executor內存比例:

       1、對於垃圾回收來說,最重要的就是調節RDD緩存佔用的內存空間,與算子執行時創建的對象佔用的內存空間的比例。默認情況下,Spark使用每個executor 60%的內存空間來緩存RDD,那麼在task執行期間創建的對象,只有40%的內存空間來存放。

 

       在這種情況下,很有可能因爲你的內存空間的不足,task創建的對象過大,那麼一旦發現40%的內存空間不夠用了,就會觸發Java虛擬機的垃圾回收操作。因此在極端情況下,垃圾回收操作可能會被頻繁觸發。

 

       2、在上述情況下,如果發現垃圾回收頻繁發生。那麼就需要對那個比例進行調優,使用newSparkConf().set("spark.storage.memoryFraction", "0.5")即可,可以將RDD緩存佔用空間的比例降低,從而給更多的空間讓task創建的對象進行使用。

 

       因此,對於RDD持久化,完全可以使用Kryo序列化,加上降低其executor內存佔比的方式,來減少其內存消耗。給task提供更多的內存,從而避免task的執行頻繁觸發垃圾回收。



發佈了17 篇原創文章 · 獲贊 0 · 訪問量 8087
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章