天天看點

Spark學習之路 (十二)SparkCore的調優之資源調優

《2021年最新版大資料面試題全面開啟更新》

歡迎關注github《大資料成神之路》

目錄

一、概述

二、Spark作業基本運作原理

三、資源參數調優

3.1 num-executors

3.2 executor-memory

3.3 executor-cores

3.4 driver-memory

3.5 spark.default.parallelism

3.6 spark.storage.memoryFraction

3.7 spark.shuffle.memoryFraction

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

二、Spark作業基本運作原理

Spark學習之路 (十二)SparkCore的調優之資源調優

詳細原理見上圖。我們使用spark-submit送出一個Spark作業之後,這個作業就會啟動一個對應的Driver程序。根據你使用的部署模式(deploy-mode)不同,Driver程序可能在本地啟動,也可能在叢集中某個工作節點上啟動。Driver程序本身會根據我們設定的參數,占有一定數量的記憶體和CPU core。而Driver程序要做的第一件事情,就是向叢集管理器(可以是Spark Standalone叢集,也可以是其他的資源管理叢集,美團•大衆點評使用的是YARN作為資源管理叢集)申請運作Spark作業需要使用的資源,這裡的資源指的就是Executor程序。YARN叢集管理器會根據我們為Spark作業設定的資源參數,在各個工作節點上,啟動一定數量的Executor程序,每個Executor程序都占有一定數量的記憶體和CPU core。

在申請到了作業執行所需的資源之後,Driver程序就會開始排程和執行我們編寫的作業代碼了。Driver程序會将我們編寫的Spark作業代碼分拆為多個stage,每個stage執行一部分代碼片段,并為每個stage建立一批task,然後将這些task配置設定到各個Executor程序中執行。task是最小的計算單元,負責執行一模一樣的計算邏輯(也就是我們自己編寫的某個代碼片段),隻是每個task處理的資料不同而已。一個stage的所有task都執行完畢之後,會在各個節點本地的磁盤檔案中寫入計算中間結果,然後Driver就會排程運作下一個stage。下一個stage的task的輸入資料就是上一個stage輸出的中間結果。如此循環往複,直到将我們自己編寫的代碼邏輯全部執行完,并且計算完所有的資料,得到我們想要的結果為止。

Spark是根據shuffle類算子來進行stage的劃分。如果我們的代碼中執行了某個shuffle類算子(比如reduceByKey、join等),那麼就會在該算子處,劃分出一個stage界限來。可以大緻了解為,shuffle算子執行之前的代碼會被劃分為一個stage,shuffle算子執行以及之後的代碼會被劃分為下一個stage。是以一個stage剛開始執行的時候,它的每個task可能都會從上一個stage的task所在的節點,去通過網絡傳輸拉取需要自己處理的所有key,然後對拉取到的所有相同的key使用我們自己編寫的算子函數執行聚合操作(比如reduceByKey()算子接收的函數)。這個過程就是shuffle。

當我們在代碼中執行了cache/persist等持久化操作時,根據我們選擇的持久化級别的不同,每個task計算出來的資料也會儲存到Executor程序的記憶體或者所在節點的磁盤檔案中。

是以Executor的記憶體主要分為三塊:第一塊是讓task執行我們自己編寫的代碼時使用,預設是占Executor總記憶體的20%;第二塊是讓task通過shuffle過程拉取了上一個stage的task的輸出後,進行聚合等操作時使用,預設也是占Executor總記憶體的20%;第三塊是讓RDD持久化時使用,預設占Executor總記憶體的60%。

task的執行速度是跟每個Executor程序的CPU core數量有直接關系的。一個CPU core同一時間隻能執行一個線程。而每個Executor程序上配置設定到的多個task,都是以每個task一條線程的方式,多線程并發運作的。如果CPU core數量比較充足,而且配置設定到的task數量比較合理,那麼通常來說,可以比較快速和高效地執行完這些task線程。

以上就是Spark作業的基本運作原理的說明,大家可以結合上圖來了解。了解作業基本原理,是我們進行資源參數調優的基本前提。

三、資源參數調優

了解完了Spark作業運作的基本原理之後,對資源相關的參數就容易了解了。所謂的Spark資源參數調優,其實主要就是對Spark運作過程中各個使用資源的地方,通過調節各種參數,來優化資源使用的效率,進而提升Spark作業的執行性能。以下參數就是Spark中主要的資源參數,每個參數都對應着作業運作原理中的某個部分,我們同時也給出了一個調優的參考值。

3.1 num-executors

  • 參數說明:該參數用于設定Spark作業總共要用多少個Executor程序來執行。Driver在向YARN叢集管理器申請資源時,YARN叢集管理器會盡可能按照你的設定來在叢集的各個工作節點上,啟動相應數量的Executor程序。這個參數非常之重要,如果不設定的話,預設隻會給你啟動少量的Executor程序,此時你的Spark作業的運作速度是非常慢的。
  • 參數調優建議:每個Spark作業的運作一般設定50~100個左右的Executor程序比較合适,設定太少或太多的Executor程序都不好。設定的太少,無法充分利用叢集資源;設定的太多的話,大部分隊列可能無法給予充分的資源。

3.2 executor-memory

  • 參數說明:該參數用于設定每個Executor程序的記憶體。Executor記憶體的大小,很多時候直接決定了Spark作業的性能,而且跟常見的JVM OOM異常,也有直接的關聯。
  • 參數調優建議:每個Executor程序的記憶體設定4G~8G較為合适。但是這隻是一個參考值,具體的設定還是得根據不同部門的資源隊列來定。可以看看自己團隊的資源隊列的最大記憶體限制是多少,num-executors乘以executor-memory,是不能超過隊列的最大記憶體量的。此外,如果你是跟團隊裡其他人共享這個資源隊列,那麼申請的記憶體量最好不要超過資源隊列最大總記憶體的1/3~1/2,避免你自己的Spark作業占用了隊列所有的資源,導緻别的同學的作業無法運作。

3.3 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左右比較合适,也是避免影響其他同學的作業運作。最好的應該就是一個cpu core對應兩到三個task

3.4 driver-memory

  • 參數說明:該參數用于設定Driver程序的記憶體。
  • 參數調優建議:Driver的記憶體通常來說不設定,或者設定1G左右應該就夠了。唯一需要注意的一點是,如果需要使用collect算子将RDD的資料全部拉取到Driver上進行處理,那麼必須確定Driver的記憶體足夠大,否則會出現OOM記憶體溢出的問題。

3.5 spark.default.parallelism

  • 參數說明:該參數用于設定每個stage的預設task數量。這個參數極為重要,如果不設定可能會直接影響你的Spark作業性能。一個分區對應一個task,也就是這個參數其實就是設定task的數量
  • 參數調優建議: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叢集的資源。

3.6 spark.storage.memoryFraction

  • 參數說明:該參數用于設定RDD持久化資料在Executor記憶體中能占的比例,預設是0.6。也就是說,預設Executor 60%的記憶體,可以用來儲存持久化的RDD資料。根據你選擇的不同的持久化政策,如果記憶體不夠時,可能資料就不會持久化,或者資料會寫入磁盤。
  • 參數調優建議:如果Spark作業中,有較多的RDD持久化操作,該參數的值可以适當提高一些,保證持久化的資料能夠容納在記憶體中。避免記憶體不夠緩存所有的資料,導緻資料隻能寫入磁盤中,降低了性能。但是如果Spark作業中的shuffle類操作比較多,而持久化操作比較少,那麼這個參數的值适當降低一些比較合适。此外,如果發現作業由于頻繁的gc導緻運作緩慢(通過spark web ui可以觀察到作業的gc耗時),意味着task執行使用者代碼的記憶體不夠用,那麼同樣建議調低這個參數的值。

3.7 spark.shuffle.memoryFraction

  • 參數說明:該參數用于設定shuffle過程中一個task拉取到上個stage的task的輸出後,進行聚合操作時能夠使用的Executor記憶體的比例,預設是0.2。也就是說,Executor預設隻有20%的記憶體用來進行該操作。shuffle操作在進行聚合時,如果發現使用的記憶體超出了這個20%的限制,那麼多餘的資料就會溢寫到磁盤檔案中去,此時就會極大地降低性能。
  • 參數調優建議:如果Spark作業中的RDD持久化操作較少,shuffle操作較多時,建議降低持久化操作的記憶體占比,提高shuffle操作的記憶體占比比例,避免shuffle過程中資料過多時記憶體不夠用,必須溢寫到磁盤上,降低了性能。此外,如果發現作業由于頻繁的gc導緻運作緩慢,意味着task執行使用者代碼的記憶體不夠用,那麼同樣建議調低這個參數的值。

繼續閱讀