Spark效能優化

Zhenng發表於2022-04-09

Spark配置介紹

  • Spark中的配置選項在四個地方可以進行配置,其中優先順序如下:

    SparkConf(程式碼) > spark-submit 或 spark-shell 命令列引數 > spark-defaults.conf > spark-env.sh > 預設值

  • 在程式碼中配置的為靜態配置,在spark-submit提交和spark-default.conf設定的引數為動態配置

  • spark的屬性大致分為兩種型別:

    1. 部署相關 如spark.driver.memory,spark.executor.instances,在sparkConfig中可能不起作用,用配置檔案和命令列設定
    2. Spark執行時控制相關 如spark.task.maxFailures,任何方式都可配置
  • 各種配置項的配置建議:

    1. 硬體資源類的(資源供給類:CPU、記憶體、磁碟、網路),spark-defaults.conf統一設定;
    2. 全域性服務類的(比如動態擴容、External shuffle services,安全,壓縮,Spark UI,等等),spark-defaults.conf統一設定;
    3. 任務粒度的配置(如Task重試、locality wait、是否推斷speculative task,等等),用命令列,或是SparkConf,推薦用SparkConf,命令列不好管理

個人實踐:在spark-env.sh中配置叢集需要的配置;在spark-default.conf中配置job需要的配置,提交job時指定配置檔案或適用預設配置檔案spark-default.conf

一、CPU配置項

cpu的利用率由兩個方面決定,要充分利用cpu要兩方面資源相匹配

  • 系統資源方面:叢集、executor中的cpu核數
  • 資料資源方面:資料分片的個數

cpu併發度(每個executor能併發執行幾個task)

# 叢集內滿配cpu核數
spark.cores.max  
# 單個Executor內cpu核數,standalone模式預設會使用全部核
spark.executor.cores   
# 單個task計算任務消耗cpu核數,預設為1且不能小於1,大於1時是task任務為多執行緒的(大部分時候不必設定)
spark.task.cpus        

資料並行度引數如下(將資料劃分為多少塊):

# 未指定分割槽數時RDD預設並行度
spark.default.parallelism    
# SparkSQL框架下,資料關聯、聚合操作時Reducer 在shuffle reduce階段預設的並行度
spark.sql.shuffle.partitions  

併發度=任務數=(spark.executor.cores)/(spark.task.cpus)

併發度基本由 spark.executor.cores 引數敲定,因為spark.task.cpus通常為1,且不能小於1,可以大於1(為了應對需要多個執行緒才能執行的任務)

Executor數=(spark.cores.max)/(spark.executor.cores)

以上是都是在standalone模式下的配置項,在yarn叢集中可直接指定executor個數:

# 在yarn叢集中指定executor的個數
spark.executor.instances 

例子:在kafka中的分割槽數就是spark拉取資料的並行度度,在拉取clickhouse資料時指定的numPartitions就是資料的並行度。

建議:將資料並行度設定為cpu核數的2-3倍,以充分利用cpu,否則可能會導致task傾斜的問題(一些executor十分繁忙另外一些executor卻沒有在執行)。將分割槽數(資料並行度)調高也會將資料的分片大小減小,使每個分片執行的更快,但太高的並行度會導致排程花費較多時間。

二、記憶體配置項

Spark 會區分堆內記憶體(On-heap Memory)和堆外記憶體(Off-heap Memory)

堆內記憶體的申請與釋放統一由 JVM 管理,堆外記憶體是 Spark 通過呼叫 Unsafe 的 allocateMemory 和 freeMemory 方法直接在作業系統記憶體中申請、釋放記憶體空間

慎用堆外記憶體,官方推薦只用堆內記憶體。堆內外空間互相隔離,堆內、堆外是以Job為粒度劃分的,也就是說,同一個Job,要麼全用堆外,要麼全用堆內。堆外、堆內的記憶體空間,是不能在同一個Job之內共享的。

堆外記憶體相關設定:

spark.memory.offHeap.enabled 預設false
spark.memory.offHeap.size    預設為0

堆外記憶體僅供瞭解,只使用堆內記憶體即可

堆內記憶體相關設定:

# 每個executor的記憶體絕對值大小,預設1g
spark.executor.memory 
# 除使用者記憶體外的計算和儲存記憶體所佔比例,預設0.6
spark.memory.fraction
# 計算和和儲存記憶體中儲存記憶體所佔比例,預設0.5
spark.memory.storageFraction

  • Reserved Memory:固定為300MB,不受開發者控制,它是 Spark預留的、用來儲存各種Spark內部物件的記憶體區域;

  • User Memory:用於儲存開發者自定義的資料結構,例如RDD運算元中引用的陣列、列表、對映等等

  • Execution Memory:用來執行分散式任務。分散式任務的計算,主要包括資料的轉換、過濾、對映、排序、聚並歸併等環節,而計算環節的記憶體消耗,統統來自Executor Memory

  • Storage Memery:用於快取分散式資料集,比如RDD Cache、廣播變數等等。RDD Cache指的是RDD物化到記憶體中的副本。在果同一個RDD被引用多次,那麼把這個RDD快取到內大幅提升作業的執行效能。

舉例:

記憶體 大小
spark.executor.memory 20GB
spark.memory.offHeap.size 10GB
spark.memory.fraction 0.8
spark.memory.storageFraction 0.6

堆內記憶體:

Reserved Memory大小:300M

User Memory大小:20 x(1-0.8)= 4GB

Storage Memeory大小:20 x 0.8 x 0.6 = 9.6 GB

Execution Memory大小:20 x 0.8 x (1-0.6)= 6.4GB

堆外記憶體:

Storage Memeory大小:10 x 0.6 = 6GB

Execution Memory大小:10 x 0.4 = 4GB

在spark1.6版本之後,記憶體靜態劃分轉換為動態管理記憶體,即 Execution Memory和Storage Memery可相互搶佔,搶佔規則如下(Execution Memeory更重要原則):

  • 如果對方的記憶體空間有空閒,雙方可以互相搶佔
  • 對於Storage Memory搶佔的 Execution Memory部分,當分散式任務有計算需要時Storage Memory必須立即歸還搶佔的記憶體,涉及的存資料要麼落盤、要麼清除
  • 對於 Execution Memory搶佔的 Storage Memory部分,即便 Storage Memory有收回記憶體的需要,也必須要等到分散式任務執行完畢才能釋放。

調優建議:

  • spark.memory.fraction可以儘可能調大,spark中使用者記憶體用不了太多,主要使用計算和儲存記憶體

  • ETL(Extract、Transform、Load)作業,業務抽取、轉換、載入,資料只處理一次,不需要快取,儲存記憶體的比率適當降低;機器學習、圖計算反覆使用資料,計算記憶體比率適當增大

  • 資料分片的大小與executor中每個核分得的記憶體大小基本相同

三、磁碟配置項

在Spark執行過程中會產生日誌和在shuffle過程中會產生中間檔案,將這些檔案存放在固態硬碟上會使Spark擁有更好的效能

配置在spark-env.sh中,服務級別的配置

# spark暫存空間目錄,存放map輸出檔案和RDDs,支援","分隔的多個目錄。shuffle輸出的檔案
SPARK_LOCAL_DIRS=
# spark的worker工作目錄,暫存空間存放全部日誌,預設SPARK_HOME/work
SPARK_WORKER_DIR=

配置在spark-defaults.conf或sparkConf中,任務級別的配置,會被SPARK_LOCAL_DIRS設定的目錄覆蓋

# spark暫存空間目錄,用來改善Shuffle中間檔案儲存,以及RDD Cache磁碟儲存
spark.local.dir 目錄

四、cache

在Spark計算過程中善用cache會極大提高效能,對重複使用的資料建議新增cache,而對只使用一兩次的資料不建議新增cache,否則不僅浪費記憶體空間而且會降低Spark執行效率

使用的建議:

  • 如果 RDD/DataFrame/Dataset 在應用中的引用次數為 1,就堅決不使用 Cache
  • 如果引用次數大於 1,且執行成本佔比超過 30%,應當考慮啟用 Cache

Cache table

# 建立臨時檢視再cache
df.createTempView("table_name")
spark.sql("cache tabel table_name")
CACHE [ LAZY ] TABLE table_identifier
    [ OPTIONS ( 'storageLevel' [ = ] value ) ] [ [ AS ] query ]
  • LAZY可選,加了之後不立刻快取,當第一次使用的時候快取。不加預設立刻快取

  • OPTIONS儲存級別,預設MEMORY_AND_DISK
    SER 字樣的表示以序列化方式儲存,不帶 SER 則表示採用物件值,序列化儲存(二進位制儲存)會節省儲存空間,但是消耗計算資源

    最常用MEMORY_ONLY 和 MEMORY_AND_DISK,它們分別是 RDD 快取和 DataFrame 快取的預設儲存級別。這兩種儲存級別都是先嚐試把資料快取到記憶體, MEMORY_AND_DISK在記憶體不足時將資料快取到磁碟

  • query 將查詢結果快取,如將testData表中查到的結果快取為testCache表

    CACHE TABLE testCache OPTIONS ('storageLevel' 'DISK_ONLY') SELECT * FROM testData;
    

Cache運算元

df.cache.count

cache操作時惰性操作,只有action運算元時才觸發計算

只有 count 才會觸發快取的完全物化,而 first、take 和 show 這 3 個運算元只會把涉及的資料物化,例如first只快取1條資料,show只快取20條資料

在此只介紹了Spark硬體方面的部分調優方法,此外還有SparkSQL、Shuffle、廣播變數等方面的調優方法

本文參考極客時間中《零基礎入門Spark》《Spark效能調優實戰》

相關文章