spark的性能優化boss版

1.分配資源
提交spark作業時,用的spark-submit shell腳本,裏面調整對應的參數

/usr/local/spark/bin/spark-submit \
--class cn.spark.sparktest.core.WordCountCluster \
--num-executors 3 \  配置executor的數量
--driver-memory 100m \  配置driver的內存(影響不大)
--executor-memory 100m \  配置每個executor的內存大小
--executor-cores 3 \  配置每個executor的cpu core數量
/usr/local/SparkTest-0.0.1-SNAPSHOT-jar-with-dependencies.jar \
2.設置spark Application的並行度
task數量,設置成spark application總cpu core數量的2~3倍,比如150個cpu core,基本要設置task數量爲300~500;
spark.default.parallelism
SparkConf conf = new SparkConf()
  .set("spark.default.parallelism", "500")
3.使用Kroy序列化
SparkConf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")

  首先第一步,在SparkConf中設置一個屬性,spark.serializer,org.apache.spark.serializer.KryoSerializer類;

  Kryo之所以沒有被作爲默認的序列化類庫的原因,就要出現了:主要是因爲Kryo要求,如果要達到它的最佳性能的話,那麼就一定要註冊你自定義的類(比如,你的算子函數中使用到了外部自定義類型的對象變量,這時,就要求必須註冊你的類,否則Kryo達不到最佳性能)。

  第二步,註冊你使用到的,需要通過Kryo序列化的,一些自定義類,SparkConf.registerKryoClasses()

  項目中的使用:
  .set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
  .registerKryoClasses(new Class[]{CategorySortKey.class})
4.使用fastutil集合工具類進行優化
介紹:
fastutil是擴展了Java標準集合框架(Map、List、Set;HashMap、ArrayList、HashSet)的類庫,提供了特殊類型的map、set、list和queue;
fastutil能夠提供更小的內存佔用,更快的存取速度;我們使用fastutil提供的集合類,來替代自己平時使用的JDK的原生的Map、List、Set,好處在於,fastutil集合類,可以減小內存的佔用,並且在進行集合的遍歷、根據索引(或者key)獲取元素的值和設置元素的值的時候,提供更快的存取速度;
fastutil也提供了64位的array、set和list,以及高性能快速的,以及實用的IO類,來處理二進制和文本類型的文件;
fastutil最新版本要求Java 7以及以上版本;
<dependency>
    <groupId>fastutil</groupId>
    <artifactId>fastutil</artifactId>
    <version>5.0.9</version>
</dependency>
5.調節數據本地化等待時長
PROCESS_LOCAL:進程本地化,代碼和數據在同一個進程中,也就是在同一個executor中;計算數據的task由executor執行,數據在executor的BlockManager中;性能最好
NODE_LOCAL:節點本地化,代碼和數據在同一個節點中;比如說,數據作爲一個HDFS block塊,就在節點上,而task在節點上某個executor中運行;或者是,數據和task在一個節點上的不同executor中;數據需要在進程間進行傳輸
NO_PREF:對於task來說,數據從哪裏獲取都一樣,沒有好壞之分
RACK_LOCAL:機架本地化,數據和task在一個機架的兩個節點上;數據需要通過網絡在節點之間進行傳輸
ANY:數據和task可能在集羣中的任何地方,而且不在一個機架中,性能最差

spark.locality.wait,默認是3s

spark.locality.wait,默認是3s;6s,10s

默認情況下,下面3個的等待時長,都是跟上面那個是一樣的,都是3s
spark.locality.wait.process
spark.locality.wait.node
spark.locality.wait.rack

new SparkConf()
  .set("spark.locality.wait", "10")
6.JVM調優降低cache操作
spark.storage.memoryFraction
0.6 -》0.5-》0.4-》0.2
7.JVM調優調節executor堆外內存與連接等待時長
提交腳本:
/usr/local/spark/bin/spark-submit \
--class com.ibeifeng.sparkstudy.WordCount \
--num-executors 80 \
--driver-memory 6g \
--executor-memory 6g \
--executor-cores 3 \
--master yarn-cluster \
--queue root.default \
--conf spark.yarn.executor.memoryOverhead=2048 \   ########
--conf spark.core.connection.ack.wait.timeout=300 \########
/usr/local/spark/spark.jar \
${1}


因爲比較實用,在真正處理大數據(不是幾千萬數據量、幾百萬數據量),幾億,幾十億,幾百億的時候。很容易碰到executor堆外內存,以及gc引起的連接超時的問題。file not found,executor lost,task lost。

8.shuffle調優
什麼是shuffle?
groupByKey,要把分佈在集羣各個節點上的數據中的同一個key,對應的values,都給集中到一塊兒,集中到集羣中同一個節點上,更嚴密一點說,就是集中到一個節點的一個executor的一個task中。

然後呢,集中一個key對應的values之後,才能交給我們來進行處理,<key, Iterable<value>>;reduceByKey,算子函數去對values集合進行reduce操作,最後變成一個value;countByKey,需要在一個task中,獲取到一個key對應的所有的value,然後進行計數,統計總共有多少個value;join,RDD<key, value>,RDD<key, value>,只要是兩個RDD中,key相同對應的2個value,都能到一個節點的executor的task中,給我們進行處理。
說明:
每一個shuffle的前半部分stage的task,每個task都會創建下一個stage的task數量相同的文件,比如下一個stage會有100個task,那麼當前stage每個task都會創建100份文件;會將同一個key對應的values,一定是寫入同一個文件中的;不同節點上的task,也一定會將同一個key對應的values,寫入下一個stage,同一個task對應的文件中。

shuffle的後半部分stage的task,每個task都會從各個節點上的task寫的屬於自己的那一份文件中,拉取key, value對;然後task會有一個內存緩衝區,然後會用HashMap,進行key, values的匯聚;(key ,values);

task會用我們自己定義的聚合函數,比如reduceByKey(_+_),把所有values進行一對一的累加;聚合出來最終的值。就完成了shuffle。
合併map端的輸出文件
new SparkConf().set("spark.shuffle.consolidateFiles", "true")
開啓shuffle map端輸出文件合併的機制;默認情況下,是不開啓的,就是會發生如上所述的大量map端輸出文件的操作,嚴重影響性能。
map端內存緩衝與reduce端內存佔比

spark.shuffle.file.buffer,默認32k
spark.shuffle.memoryFraction,0.2
HashShuffleManager與SortShuffleManager

spark.shuffle.manager:hash、sort、tungsten-sort(自己實現內存管理)
spark.shuffle.sort.bypassMergeThreshold:200

spark.shuffle.manager:hash、sort、tungsten-sort

new SparkConf().set("spark.shuffle.manager", "hash")
new SparkConf().set("spark.shuffle.manager", "tungsten-sort")

// 默認就是,new SparkConf().set("spark.shuffle.manager", "sort")
new SparkConf().set("spark.shuffle.sort.bypassMergeThreshold", "550")
9.算子操作之MapPartitions提升map類操類操作性能
特別說明:容易oom
filter過後使用coalesce減少分區數量
coalesce算子
主要就是用於在filter操作之後,針對每個partition的數據量各不相同的情況,來壓縮partition的數量。減少partition的數量,而且讓每個partition的數據量都儘量均勻緊湊。
foreachPartition優化寫入數據庫性能
對於我們寫的function函數,就調用一次,一次傳入一個partition所有的數據,主要創建或者獲取一個數據庫連接就可以,只要向數據庫發送一次SQL語句和多組參數即可
repartition解決spark sql低並行度問題
設置並行度
根據你的application的總cpu core數量(在spark-submit中可以指定,200個),自己手動設置spark.default.parallelism參數,指定爲cpu core總數的2~3倍。400~600個並行度。600
repartition算子,你用Spark SQL這一步的並行度和task數量,肯定是沒有辦法去改變了。但是呢,可以將你用Spark SQL查詢出來的RDD,使用repartition算子,去重新進行分區,此時可以分區成多個partition,比如從20個partition,分區成100個。

然後呢,從repartition以後的RDD,再往後,並行度和task數量,就會按照你預期的來了。就可以避免跟Spark SQL綁定在一個stage中的算子,只能使用少量的task去處理大量數據以及複雜的算法邏輯。
reduceByKey的本地聚合
val lines = sc.textFile("hdfs://")
val words = lines.flatMap(_.split(" "))
val pairs = words.map((_, 1))
val counts = pairs.reduceByKey(_ + _)
counts.collect()

10.trubleshootin控控制shuffle reduce的緩衝池大小避免OOM
spark.reducer.maxSizeInFlight,48
spark.reducer.maxSizeInFlight,24


jvm gc導致shuffle的文件拉取失敗
spark.shuffle.io.maxRetries 3

第一個參數,意思就是說,shuffle文件拉取的時候,如果沒有拉取到(拉取失敗),最多或重試幾次(會重新拉取幾次文件),默認是3次。

spark.shuffle.io.retryWait 5s

第二個參數,意思就是說,每一次重試拉取文件的時間間隔,默認是5s鍾。

默認情況下,假如說第一個stage的executor正在進行漫長的full gc。第二個stage的executor嘗試去拉取文件,結果沒有拉取到,默認情況下,會反覆重試拉取3次,每次間隔是五秒鐘。最多隻會等待3 * 5s = 15s。如果15s內,沒有拉取到shuffle file。就會報出shuffle file not found。

針對這種情況,我們完全可以進行預備性的參數調節。增大上述兩個參數的值,達到比較大的一個值,儘量保證第二個stage的task,一定能夠拉取到上一個stage的輸出文件。避免報shuffle file not found。然後可能會重新提交stage和task去執行。那樣反而對性能也不好。

spark.shuffle.io.maxRetries 60
spark.shuffle.io.retryWait 60s

最多可以忍受1個小時沒有拉取到shuffle file。只是去設置一個最大的可能的值。full gc不可能1個小時都沒結束吧。

這樣呢,就可以儘量避免因爲gc導致的shuffle file not found,無法拉取到的問題。
YARN隊列不足導致application直接失敗
ExecutorService threadPool = Executors.newFixedThreadPool(1);

threadPool.submit(new Runnable() {

@Override
public void run() {

}

});
一個 線程池對應一個資源隊列

yarn-cluster模式的jvm的棧內存溢出問題

如何解決這種問題?

既然是JVM的PermGen永久代內存溢出,那麼就是內存不夠用。咱們呢,就給yarn-cluster模式下的,driver的PermGen多設置一些。

spark-submit腳本中,加入以下配置即可:
--conf spark.driver.extraJavaOptions="-XX:PermSize=128M -XX:MaxPermSize=256M"

這個就設置了driver永久代的大小,默認是128M,最大是256M。那麼,這樣的話,就可以基本保證你的spark作業不會出現上述的yarn-cluster模式導致的永久代內存溢出的問題。

錯誤的持久化方式以及checkpoint的使用
usersRDD
usersRDD = usersRDD.cache()
val cachedUsersRDD = usersRDD.cache()
進行checkpoint,就是說,會將RDD的數據,持久化一份到容錯的文件系統上(比如hdfs)
checkpoint的原理
1、在代碼中,用SparkContext,設置一個checkpoint目錄,可以是一個容錯文件系統的目錄,比如hdfs;
2、在代碼中,對需要進行checkpoint的rdd,執行RDD.checkpoint();
3、RDDCheckpointData(spark內部的API),接管你的RDD,會標記爲marked for checkpoint,準備進行checkpoint
4、你的job運行完之後,會調用一個finalRDD.doCheckpoint()方法,會順着rdd lineage,回溯掃描,發現有標記爲待checkpoint的rdd,就會進行二次標記,inProgressCheckpoint,正在接受checkpoint操作
5、job執行完之後,就會啓動一個內部的新job,去將標記爲inProgressCheckpoint的rdd的數據,都寫入hdfs文件中。(備註,如果rdd之前cache過,會直接從緩存中獲取數據,寫入hdfs中;如果沒有cache過,那麼就會重新計算一遍這個rdd,再checkpoint)
6、將checkpoint過的rdd之前的依賴rdd,改成一個CheckpointRDD*,強制改變你的rdd的lineage。後面如果rdd的cache數據獲取失敗,直接會通過它的上游CheckpointRDD,去容錯的文件系統,比如hdfs,中,獲取checkpoint的數據。
11數據傾斜解決方案
聚合數據源以及過濾導致傾斜的key
 1
按key來分組,將key對應的所有的values,全部用一種特殊的格式,拼接到一個字符串裏面去,比如“key=sessionid, value: action_seq=1|user_id=1|search_keyword=火鍋|category_id=001;action_seq=2|user_id=1|search_keyword=涮肉|category_id=001”。
2
粗粒度進行聚合
按照省,市,區,或者天,時間等,過濾調傾斜的key.
提高shuffle操作reduce並行度
spark.default.parallelism,100
使用隨機key實現雙重聚合
使用場景
(1)groupByKey
(2)reduceByKey

比較適合使用這種方式;join,咱們通常不會這樣來做,後面會講三種,針對不同的join造成的數據傾斜的問題的解決方案。

第一輪聚合的時候,對key進行打散,將原先一樣的key,變成不一樣的key,相當於是將每個key分爲多組;

先針對多個組,進行key的局部聚合;接着,再去除掉每個key的前綴,然後對所有的key,進行全局的聚合。

對groupByKey、reduceByKey造成的數據傾斜,有比較好的效果。

將reduce join 轉換爲map join的核心是不走shuffle,直接走map.

sample採樣傾斜key進行單獨進行join

使用隨機數以及擴容表進行join
sample採樣傾斜key並單獨進行join

將key,從另外一個RDD中過濾出的數據,可能只有一條,或者幾條,此時,咱們可以任意進行擴容,擴成1000倍。

將從第一個RDD中拆分出來的那個傾斜key RDD,打上1000以內的一個隨機數。

這種情況下,還可以配合上,提升shuffle reduce並行度,join(rdd, 1000)。通常情況下,效果還是非常不錯的。

打散成100份,甚至1000份,2000份,去進行join,那麼就肯定沒有數據傾斜的問題了吧。














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