Spark性能問題及調優方法

       Spark性能問題及調優方法

1.Spark算子調優最佳實踐

  • 1.1 使用mapPartitions取代map操作

    • 如果在映射過程中需要頻繁創建額外的對象,使用mapPartitions要比使用map高效。
      例1:將RDD中的所有數據通過JDBC連接寫入數據庫,如果使用map函數,那麼每條數據都需要創建一個連接,開銷很大;而如果使用mapPartitions,則只需要每個分區創建一個連接即可。
      例2:每條數據都要創建一個列表對象,而如果使用mapPartitions則只需要對每個分區創建一個列表對象。
      rdd.map(lambda x:[x[0],x[1]]).reduceByKey(lambda …)。
  • 1.2 使用foreachPartition取代foreach操作

    • 原理與map和mapPartitions一致。
  • 1.3 使用coalesce取代rePartition操作

    • 因爲coalesce操作不會產生shuffle,而rePartition會。在分區數量變化不劇烈時,常使用coalesce。
      :對RDD通過filter過濾掉較多數據時,可以通過coalesce來合併小文件。rdd.filter(lambda x:…).coalesce(10)
  • 1.4 使用reduceByKey、AggregateByKey取代groupByKey操作

    • reduceByKey會先在本地對數據進行聚合,相較於groupByKey,可以減少I/O操作。
  • 1.5 使用廣播變量 broadcast

    • 如果Executor需要訪問Driver端的數據,在未使用廣播變量時,有多少個task,則Driver端的數據就會被複制多少份;
    • 當使用廣播變量時,一個Executor上只會有一個數據的副本。
    • :一個Application中,有10個Executor、100個Task,一個數據副本100M。如果不使用廣播,則每個Task會得到一個數據副本,因此網絡傳輸需要消耗100*100M的內存;而如果使用廣播,則每個Executor會得到一個數據副本,因此網絡傳輸需要消耗10*100M內存,對資源的消耗大大降低。
    • 注:廣播變量的級別是隻讀。
  • 1.6 Join不產生Shuffle的原理

    • join不產生shuffle的算法稱爲map-side join,它應用在大表與小表的join,所謂的小表指的是能存放於內存的表。
      具體步驟:1.將小表通過broadcast廣播給所有的Executor;
             2.在map端進行join。
  • 1.7 RDD的複用

    • Spark默認每次對一個RDD執行一個算子操作時都會從源頭處計算一遍該RDD,當一個RDD在整個計算過程中需要被重複使用時,可以考慮對該RDD進行持久會操作,即將該RDD保存在內存或磁盤中,這樣在下次複用時就可以直接提取該RDD的數據而不需要根據該RDD的血統,從源頭開始重新計算該RDD了。
    • 進行持久化的算子有persist和cache。
      cache操作是persist的一個簡易版本,它默認將RDD持久化到內存中;而persist是一個手動持久化算子,它可以手工選擇持久化級別,包括是持久化到內存還是硬盤、是否序列化以及序列化的方式。
      序列化操作的作用是減少RDD佔用的內存或磁盤空間,避免發生頻繁的垃圾回收(GC)。但是序列化要比不序列化耗時,採取什麼序列化方式也是一個調優方式。類似的,持久化也需要花費一定的時間,因此,除非RDD重複使用,否則即便內存再充足也不應隨便持久化。

2.Spark頻繁遇到的性能問題及調優技巧

  • 2.1 對於Driver端的數據通過廣播變量進行分發
    • 原理即爲1.5的說明
  • 2.2 使用Kryo序列化代替Java序列化(默認)
    • Spark默認的序列化方式是Java序列化,Java序列化非常靈活,但是速度較慢,在某些情況下序列化的結果也比較大。
    • Spark 2.X版本可以使用Kryo序列化對象。Kryo序列化速度快,結果緊湊,但是不支持所有類型,爲了提高性能,需要提前註冊程序中使用的類(class),不註冊速度性能好像極差。
    • 通過conf.set(“spark.serializer”,“org.apache.SPark.serializer.KryoSerializer”)來使用Kryo序列化,並通過conf.registerKryoClasses(Array(classOf[類1],classOf[類2])來進行註冊。
  • 2.3 checkpoint的正確使用
    • checkpoint即檢查點,是一個將某個中間RDD保存在本地文件夾或者HDFS的過程,該操作的目的是防止數據丟失。
    • checkpoint與cache或者persist的異同
      同:都是保存RDD的中間結果,防止RDD丟失導致數據重複計算。
      異:持久化(cache或persist)雖然是將RDD保存到內存或者硬盤當中,但是該保存的數據仍然是由blockManager管理,當Application進程結束以後,持久化的數據將會被清除(包括內存級別和硬盤級別);而checkpoint則是將RDD寫入本地文件夾或者HDFS,當Application進程結束,數據仍然存在。
    • 調用checkpoint會觸發一個job,因此在checkpoint之前,需要將需要checkpoint的RDD進行persist持久化,否則checkpoint完後,又得根據RDD的血統從源頭開始重新計算一遍該RDD。

3. Spark集羣資源分配及並行度調優

  • 3.1 Spark內存模型

    • 影響Executor內存的參數主要有兩個:
      spark.executor.memory(堆內內存)
      spark.yarn.executor.memoryOverhead(堆外內存)
      內存申請時,應保證 堆內內存 + 堆外內存 <= yarn.scheduler.maximum-allocation-mb
    • 堆外內存:
      主要用於JVM運行的開銷
    • 堆內內存:
      堆內內存被劃分爲:存儲內存+計算內存+其它內存+保留內存
    • 1.存儲內存用於存放持久化(cache、persist)及廣播(broadcast)的數據;
    • 2.計算內存用於拉取上一個stage的輸出,以及進行聚合等操作時使用;
    • 3.其它內存用於存放元數據等;
    • 4.保留內存用於spark對內存不精確計算時的溢出情況的優化。
  • 3.2 Spark並行度設置

    • 並行度(其實就是RDD的Partition數量,一個Partition對應一個Task)就是Spark作業中,各個階段(stage)的Task的數量,也就代表了Spark在各個Stage的並行度。
    • 並行度的作用
      考慮一個並行度設置不當的情況:
      假設一個stage有100個Task,現有150個core的資源(spark中一個core同時處理一個task)。在計算時雖然有150core,但此時的並行度只能達到100,有50個core的資源將被閒置。如果將Task數量增加到150以上,這樣就可以同時調動150個core進行計算。一般並行度設置爲core總數的2-3倍。

4. Spark集羣中Mapper端、Reducer端內存調優

  • 4.1 Shuffle過程中,Mapper端做了什麼?

    • Mapper端主要就是爲Reducer端提供數據,具體過程如下:
      1.map將RDD中的每一行數據轉化爲(key,value)的鍵值對形式,但並不會馬上將該鍵值對寫入到磁盤,而是放在了“環形內存緩衝區”,並在此刻給每個key-value對一個partitionId,當緩衝區內存達到閾值後,便將緩衝區的數據寫入到磁盤的臨時文件,寫入前會對key值進行排序。
      2.當map全執行完後,此時每個節點上會有多個key-value的臨時文件,因此需要對多個臨時文件進行聚合,聚合時相同partitionId的文件合併到一起,並對各個partitionId中的key進行排序,到這一步,Mapper端的ShuffleWrite就完成了。
    • 調優:合理設置緩存層的大小,來避免頻繁進行磁盤訪問
      參數:spark.shuffle.file.buffer(可通過log信息來調整合理的buffer)
    • 注:Mapper端具體會將數據分成幾個partition取決於Reducer端的並行度。
  • 4.2 Shuffle過程中的Reducer端

    • Mapper端已經對數據按key分好區了,並且會產生一個索引文件,記錄分區的偏移量,Reducer只需要按照索引文件去Mapper端拉取自己所需要的數據即可。
  • 4.3 Sorted-Based Shuffle和Tungsten-Sorted Shuffle在Mapper和Reducer端的區別

    • Spark Sorted-Based Shuffle在Mapper端進行排序,包括Partition的排序和每個Partition內部元素的排序,但是Reducer端沒有進行排序,所以Job的結果默認不是排序的。Sorted-Based Shuffle採用Tim-Sort排序算法,好處是可以極爲高效地使用Mapper端的排序成果全局排序。
    • Tungsten-Sorted Shuffle在Mapper中不會對內部元素進行排序(它只會對Partition進行排序)。
    • Tungsten-Sorted Shuffle在程序有Aggregate的時候就退化爲Sorted-Based Shuffle,或者是Mapper端輸出的Partition大於16777216時,或者是一條Record大於128M時。
  • 4.3 內存性能調優

  1. Reducer端的業務邏輯(Business Logic)運行的空間不足,業務邏輯運行的時候被迫把數據溢出到磁盤上面,一方面造成了業務邏輯處理的時候需要讀寫磁盤令一方面也會導致不安全。
    解決辦法:調整spark.shuffle.memoryFraction 。調整得越大,溢寫到磁盤的次數就越少,效率越高。
  2. 發生Reducer端的OOM,Reducer端如果出現OOM,一般有內存中數據太多,無法容納活躍的對象。
    解決辦法:調小Reducer端的緩存層。該操作在減緩OOM的同時會導致Reducer向Mapper端拉取數據的次數變多,性能下降。
  3. shuffle file not found
    調優方法:可能是GC,當Executor進行GC時,所有的現成都停止工作,所以暫時無法獲取數據。Reducer如果在此時想Mapper獲取數據,可能會得不到響應,因此報錯。解決辦法就是增大響應的等待時間及重試次數。
    spark.shuffle.io.maxRetries
    spark.shuffle.io.retryWait

5.數據傾斜調優

  • 5.1 何爲數據傾斜
    • 數據傾斜指的是在並行過程中,某個Task處理的數據量遠遠大於其它Task處理的數據量。
  • 5.2 數據傾斜對性能的影響
    • 1.一個最基本的影響就是,降低了計算效率。比如有10個Task,其中9個Task數據量很少,處理一個Task只需要1min,而有一個Task的數據量巨大,處理需要20min,那麼整個計算任務在處理完9個小Task後還得繼續等待第10個Task,效率急劇下降。
    • 2.比降低計算效率更壞的結果就是直接內存溢出(OOM)。1中雖然效率降低,但是好歹能計算出結果,而如果數據量大到直接報OOM,則整個程序連結果都得不到。
  • 5.3 導致數據傾斜的原因
    • 1.平時HDFS對數據塊的劃分一般都是均勻的,即等量分塊。而在Shuffle過程中,相同key值的數據一般會分配給同一個Task進行運算,如果某個key的數據量過大,則在次階段容易產生數據傾斜。
  • 5.4 處理辦法
    • 1.如果數據量很大的key是一些無用的key,如null,-1的等,可以直接通過filter先過濾掉再進行計算。
    • 2.將數據傾斜發生過程上移到Hive端。Hive是底層封裝了Hadoop的數據倉庫處理工具,適合用於大數據集的批處理作業,將數據傾斜的操作前移到Hive中進行,在Hive中對數據傾斜的記錄進行預處理,就可以從數據根源上解決了數據傾斜的問題。
    • 3.隨機Key雙重聚合
      隨機Key雙重聚合是指給每個key一個隨機前綴,對key進行二次聚合。該方法適用於reduceByKey、GroupByKey等情景
      (1)第一次聚合(局部聚合):給每個key加上一個隨機前綴,然後進行第一次聚合;
      (2)第二次聚合(雙重聚合):去掉key的隨機前綴,進行第二次聚合,得到的結果即爲最終結果。
      可以從爲什麼reduceByKey要優於GroupByKey的角度來解析隨機Key雙重聚合的有效性
    • 4.提高Reducer端的並行度
      如果某個Task在處理時有100個key,且每個key的數據量都很大,要是在Reducer端提高並行度,把100個key分給10個Task,則此時每個Task處理的數據都將下降。(該方法不能解決單個key的數據傾斜問題)
      對於reduceByKey,可以傳入參數numPartitions來指定並行度
    • 5.Join過程中用無Shuffle方法實現
      即1.6介紹,該方法避免了Shuffle,所以可以在一定程度上解決數據傾斜問題。
發佈了58 篇原創文章 · 獲贊 18 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章