Spark調優技巧總結

Spark 調優

避免創建重複RDD

  • 同一份數據只創建一個RDD
  • 避免重複計算

儘量複用同一個RDD

  • 數據存在包含關係或者重複的情況下儘量複用RDD
  • 避免重複計算

對多次使用的RDD進行持久化

  • 由於RDD是惰性計算,執行RDD需要從源頭算起,因此可以對多次重複使用的RDD進行Persist或者Cache,將當前RDD保存到內存或者磁盤中
  • 避免重複計算
  • 持久化級別後綴_2(如MEMORY_ONLY_2)表示將每個持久化數據複製1份duplicate
  • 選取持久化策略:具體根據內存估計去選擇

儘量避免使用shuffle類算子

  • shuffle類算子有:
    • 去重:
      • distinct
    • 聚合
      • reduceByKey
      • groupBy
      • cogroup
      • aggregateByKey
      • combineByKey
    • 排序
      • sortByKey
      • sortBy
    • 重分區
      • coalesce
      • repartition
    • 集合或表操作
      • intersection
      • substract
      • subtractByKey
      • join
      • leftOuterJoin
  • shuffle簡單來說會將分佈在多個節點上的數據拉去到同一節點進行聚合,會引發大量網絡傳輸,且如果某個節點key值過多而不夠內存則會溢寫到磁盤文件造成IO,以上兩者是shuffle性能較差的主要原因。

使用map-side預聚合

  • 類似於MR的本地combiner
  • 通過本地節點預先進行根據key值聚合,大大減少shuffle時需要拉去的數據量從而減小磁盤IO和網絡傳輸開銷
  • 通常建議使用reduceByKey和aggregateByKey來替代groupByKey,因爲reduceByKey和aggregateByKey會使用用戶自定義函數進行節點的本地預聚合,而groupByKey是不會的

mapPartition替代普通map

  • 使用一次函數處理一個partition所有的數據相對於一次函數一條一條處理性能會高一些,但有時候一次性處理整個partition的數據容易造成OOM。
  • Warning:預估內存大小

使用foreachPartition替代foreach

  • 原理類似上一條
  • 在需要對RDD中的數據進行數據庫讀寫時尤其推薦foreachPartition,如果使用foreach,可能會每一條數據都創建一個數據庫連接,這樣會頻繁地創建和小會數據庫連接,性能會非常差,而使用foreachPartition則可以使用一個鏈接處理整個分區的數據,性能相對高很多。

filter後進行coalesce操作

  • filter過濾數據後建議使用coalesce減少分區,能夠減少後續stage處理的task的數量進而提升性能

使用repartitionAndSortWithinPartitions替代repartition與sort類操作

  • 官方建議,如果需要在repartition重分區之後,還要進行排序,建議直接使用repartitionAndSortWithinPartitions算子。因爲該算子可以一邊進行重分區的shuffle操作,一邊進行排序。shuffle與sort兩個操作同時進行,比先shuffle再sort來說,性能可能是要高的。

廣播大變量

  • Map-side Join
    • 大表join小表時: 將小表廣播(Broadcast)到各個Executor,大表RDD使用Map手動進行join
  • 在算子函數中使用到外部變量時
    • 默認情況下,Spark會將該變量複製多個副本,通過網絡傳輸到task中,此時每個task都有一個變量副本,一個Executor就會有多份變量。使用Spark的廣播功能後,對該變量進行廣播。廣播後的變量,會保證每個Executor的內存中,只駐留一份變量副本,而Executor中的task執行時共享該Executor中的那份變量副本。這樣的話,可以大大減少變量副本的數量,從而減少網絡傳輸的性能開銷,並減少對Executor內存的佔用開銷,降低GC的頻率。

使用使用Kryo優化序列化性能

  • 在Spark中,主要有三個地方涉及到了序列化: * 在算子函數中使用到外部變量時,該變量會被序列化後進行網絡傳輸(見“原則七:廣播大變量”中的講解)。 * 將自定義的類型作爲RDD的泛型類型時(比如JavaRDD,Student是自定義類型),所有自定義類型對象,都會進行序列化。因此這種情況下,也要求自定義的類必須實現Serializable接口。 * 使用可序列化的持久化策略時(比如MEMORY_ONLY_SER),Spark會將RDD中的每個partition都序列化成一個大的字節數組。

Stage劃分

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

總結自:
美團技術團隊:Spark優化-基礎篇

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章