1.shuffle原理
什麼樣的情況下,會發生shuffle?
在spark中,主要是以下幾個算子:groupByKey、reduceByKey、countByKey、join,等等。
什麼是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來完成的。因爲這其實是個逆向的過程,不是stage決定shuffle,是shuffle決定stage。
reduceByKey(_+_),在某個action觸發job的時候,DAGScheduler,會負責劃分job爲多個stage。劃分的依據,就是,如果發現有會觸發shuffle操作的算子,比如reduceByKey,就將這個操作的前半部分,以及之前所有的RDD和transformation操作,劃分爲一個stage;shuffle操作的後半部分,以及後面的,直到action爲止的RDD和transformation操作,劃分爲另外一個stage。
每一個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。
shuffle前半部分的task在寫入數據到磁盤文件之前,都會先寫入一個一個的內存緩衝,內存緩衝滿溢之後,再spill溢寫到磁盤文件中。
2.shuffle的map端的文件合併
2.1. 默認的這種shuffle行爲,對性能有什麼樣的惡劣影響
實際生產環境的條件:
100個節點(每個節點一個executor):100個executor
每個executor:2個cpu core
總共1000個task:每個executor平均10個task
每個節點,10個task,每個節點會輸出多少份map端文件?10 * 1000=1萬個文件
總共有多少份map端輸出文件?100 * 10000 = 100萬。
第一個stage,每個task,都會給第二個stage的每個task創建一份map端的輸出文件
第二個stage,每個task,會到各個節點上面去,拉取第一個stage每個task輸出的,屬於自己的那一份文件。
所以shuffle中的寫磁盤的操作,基本上就是shuffle中性能消耗最爲嚴重的部分。
2.2.開啓合併文件功能
new SparkConf().set("spark.shuffle.consolidateFiles", "true")
開啓shuffle map端輸出文件合併的機制;默認情況下,是不開啓的,就是會發生如上所述的大量map端輸出文件的操作,嚴重影響性能。
3.Map端內存緩衝與reduce端內存佔比調節
3.1.map端內存緩衝
默認情況下,shuffle的map task,輸出到磁盤文件的時候,統一都會先寫入每個task自己關聯的一個內存緩衝區。這個緩衝區大小,默認是32kb。每一次,當內存緩衝區滿溢之後,纔會進行spill操作,溢寫操作,溢寫到磁盤文件中去
3.2.reduce端內存佔比
reduce端task,在拉取到數據之後,會用hashmap的數據格式,來對各個key對應的values進行匯聚。針對每個key對應的values,執行我們自定義的聚合函數的代碼,比如_ + _(把所有values累加起來)。reduce task,在進行匯聚、聚合等操作的時候,實際上,使用的就是自己對應的executor的內存,executor(jvm進程,堆),默認executor內存中劃分給reduce task進行聚合的比例,是0.2。
問題來了,因爲比例是0.2,所以,理論上,很有可能會出現,拉取過來的數據很多,那麼在內存中,放不下;這個時候,默認的行爲,就是說,將在內存放不下的數據,都spill(溢寫)到磁盤文件中去。
3.3.調優
默認,map端內存緩衝是每個task,32kb。
默認,reduce端聚合內存比例,是0.2,也就是20%。
如果map端的task,處理的數據量比較大,但是呢,你的內存緩衝大小是固定的。可能會出現什麼樣的情況?
每個task就處理320kb,32kb,總共會向磁盤溢寫320 / 32 = 10次。
每個task處理32000kb,32kb,總共會向磁盤溢寫32000 / 32 = 1000次。
在map task處理的數據量比較大的情況下,而你的task的內存緩衝默認是比較小的,32kb。可能會造成多次的map端往磁盤文件的spill溢寫操作,發生大量的磁盤IO,從而降低性能。
reduce端聚合內存,佔比。默認是0.2。如果數據量比較大,reduce task拉取過來的數據很多,那麼就會頻繁發生reduce端聚合內存不夠用,頻繁發生spill操作,溢寫到磁盤上去。而且最要命的是,磁盤上溢寫的數據量越大,後面在進行聚合操作的時候,很可能會多次讀取磁盤中的數據,進行聚合。
默認不調優,在數據量比較大的情況下,可能頻繁地發生reduce端的磁盤文件的讀寫。
這兩個點之所以放在一起講,是因爲他們倆是有關聯的。數據量變大,map端肯定會出點問題;reduce端肯定也會出點問題;出的問題是一樣的,都是磁盤IO頻繁,變多,影響性能。
調節map task內存緩衝:spark.shuffle.file.buffer,默認32k(spark 1.3.x不是這個參數,後面還有一個後綴,kb;spark 1.5.x以後,變了,就是現在這個參數)
調節reduce端聚合內存佔比:spark.shuffle.memoryFraction,0.2
3.4. 在實際生產環境中,我們在什麼時候來調節兩個參數?
看Spark UI,如果你的公司是決定採用standalone模式,那麼狠簡單,你的spark跑起來,會顯示一個Spark UI的地址,4040的端口,進去看,依次點擊進去,可以看到,你的每個stage的詳情,有哪些executor,有哪些task,每個task的shuffle write和shuffle read的量,shuffle的磁盤和內存,讀寫的數據量;如果是用的yarn模式來提交,課程最前面,從yarn的界面進去,點擊對應的application,進入Spark UI,查看詳情。
如果發現shuffle 磁盤的write和read,很大。這個時候,就意味着最好調節一些shuffle的參數。進行調優。首先當然是考慮開啓map端輸出文件合併機制。
調節上面說的那兩個參數。調節的時候的原則。spark.shuffle.file.buffer,每次擴大一倍,然後看看效果,64,128;spark.shuffle.memoryFraction,每次提高0.1,看看效果。
不能調節的太大,太大了以後過猶不及,因爲內存資源是有限的,你這裏調節的太大了,其他環節的內存使用就會有問題了。
調節了以後,效果?map task內存緩衝變大了,減少spill到磁盤文件的次數;reduce端聚合內存變大了,減少spill到磁盤的次數,而且減少了後面聚合讀取磁盤文件的數量。
4.HashShuffleManager與SortShuffleManager
之前我們所講的,其實都是已經屬於Spark中,比較老舊的一種shuffle manager,HashShuffleManager;這種manager,實際上,從spark 1.2.x版本以後,就不再是默認的選擇了。
HashShuffleManager的原理,以及對應的一些性能調優的點,基本上,之前幾講,咱們就都講過了。
spark 1.2.x版本以後,默認的shuffle manager,是什麼呢?SortShuffleManager。
SortShuffleManager與HashShuffleManager兩點不同:
1、SortShuffleManager會對每個reduce task要處理的數據,進行排序(默認的)。
2、SortShuffleManager會避免像HashShuffleManager那樣,默認就去創建多份磁盤文件。一個task,只會寫入一個磁盤文件,不同reduce task的數據,用offset來劃分界定。之前講解的一些調優的點,比如consolidateFiles機制、map端緩衝、reduce端內存佔比。這些對任何shuffle manager都是有用的。
在spark 1.5.x以後,對於shuffle manager又出來了一種新的manager,tungsten-sort(鎢絲),鎢絲sort shuffle manager。官網上一般說,鎢絲sort shuffle manager,效果跟sort shuffle manager是差不多的。
但是,唯一的不同之處在於,鎢絲manager,是使用了自己實現的一套內存管理機制,性能上有很大的提升, 而且可以避免shuffle過程中產生的大量的OOM,GC,等等內存相關的異常。
5.reduce端緩衝大小調優
map端的task是不斷的輸出數據的,數據量可能是很大的。但是,其實reduce端的task,並不是等到map端task將屬於自己的那份數據全部寫入磁盤文件之後,再去拉取的。map端寫一點數據,reduce端task就會拉取一小部分數據,立即進行後面的聚合、算子函數的應用。每次reduece能夠拉取多少數據,就由buffer來決定。因爲拉取過來的數據,都是先放在buffer中的。然後才用後面的executor分配的堆內存佔比(0.2),hashmap,去進行後續的聚合、函數的執行。
5.1.調優
咱們假如說,你的Map端輸出的數據量也不是特別大,然後你的整個application的資源也特別充足。200個executor、5個cpu core、10G內存。其實可以嘗試去增加這個reduce端緩衝大小的,比如從48M,變成96M。那麼這樣的話,每次reduce task能夠拉取的數據量就很大。需要拉取的次數也就變少了。比如原先需要拉取100次,現在只要拉取50次就可以執行完了。對網絡傳輸性能開銷的減少,以及reduce端聚合操作執行的次數的減少,都是有幫助的。最終達到的效果,就應該是性能上的一定程度上的提升。
一定要注意,資源足夠的時候,再去做這個事兒。