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優化-基礎篇