MapReduce的shuffle的計算過程是在executor中劃分mapper與reducer,可以作為對比參考。
Spark的Shuffle中有兩個重要的壓縮參數(shù):
設置spark.shuffle.compress=true:是否將會將shuffle中outputs的過程進行壓縮。
可將spark.io.compression.codec 編碼器設置數(shù)據(jù)壓縮格式。
通過spark.shuffle.manager 來設置shuffle時的排序算法,有hash,sort,tungsten-sort。(用hash會快一點,因為不需要排序)
Hash Shuffle 輸出中間數(shù)據(jù)
使用hash散列有很多缺點,主要是因為每個Map task都會為每個reduce生成一份文件,所以最后就會有M * R個文件數(shù)量,與executor數(shù)量和core數(shù)量沒有關系。那么如果在比較多的Map數(shù)量和Reduce數(shù)量的情況下就會出問題,輸出緩沖區(qū)的大小,系統(tǒng)中打開文件的數(shù)量,創(chuàng)建和刪除所有這些文件的速度都會受到影響。如下圖:
這里有一個優(yōu)化的參數(shù)spark.shuffle.consolidateFiles,默認為false。當設置成true時,會對mapper output時的文件進行合并。如果你集群有E個executors(“-num-excutors”)以及C個cores("-executor-cores”),以及每個task有T個CPUs(“spark.task.cpus”),那么總共的execution的slot在集群上的個數(shù)就是E * C / T(也就是executor個數(shù)×CORE的數(shù)量/CPU個數(shù))個,那么shuffle過程中所創(chuàng)建的文件就為E * C / T * R(也就是executor個數(shù) × core的個數(shù)/CPU個數(shù)×Reduce個數(shù))個。
#補充說明:spark.task.cpus默認值為1,表示number of cores to allocate for each task。
文獻中都寫的太過公式化,此處用通俗易懂的形式闡述下。就好比總共的并行度是20(5個executor,每個executor有4個core) ?Map階段會將數(shù)據(jù)寫入磁盤,當它完成時,他將會以Reduce的個數(shù)來生成文件數(shù)。那么每個executor就只會計算core的數(shù)量/spark.task.cpus個數(shù)的tasks。如果task數(shù)量大于總共集群并行度,那么將開啟下一輪輪詢執(zhí)行。HashShuffle的執(zhí)行速度較快,因為沒有再對中間結果進行排序,減少了reduce打開文件時的性能消耗。
當數(shù)據(jù)是經(jīng)過序列化以及壓縮的,重新讀取文件時,數(shù)據(jù)將進行解壓縮與反序列化,這里reduce端數(shù)據(jù)的拉取有個參數(shù)spark.reducer.maxSizeInFlight(默認為48MB),它將決定每次數(shù)據(jù)從遠程的executors中拉取大小。這個拉取過程是由5個并行的request,從不同的executor中拉取過來,從而提升了fetch的效率。 如果你加大了這個參數(shù),那么reducers將會請求更多的文數(shù)據(jù)進來,它將提高性能,但是也會增加reduce時的內(nèi)存開銷。
Sort Shuffle 輸出中間數(shù)據(jù)
Sort Shuffle如同hash shuffle的Map輸出數(shù)據(jù)到磁盤,Reduce拉取數(shù)據(jù)的一個性質(zhì),當在進行SortShuffle時,總共的Reducers要小于spark.shuffle.sort.bypassMergeThrshold(默認為200),將會執(zhí)行回退計劃,使用HashShuffle將數(shù)據(jù)寫入單獨的文件中,然后將這些小文件聚集到一個文件中,從而加快了效率。(實現(xiàn)自BypassMergeSortShuffleWriter中)
那么它的實現(xiàn)邏輯是在Reducer端合并Mappers的輸出結果。Spark在reduce端的排序是用了TimSort,它就是在reduce前,提前用算法進行了排序。那么用算法的思想來說,合并的M*N個元素進行排序,那么其復雜度為O(MNlogM),具體算法不講了。
隨之,當你沒有足夠的內(nèi)存保存map的輸出結果時,在溢出前,會將它們spill到磁盤,那么緩存到內(nèi)存的大小便是?spark.shuffle.memoryFraction * spark.shuffle.safetyFraction。默認的情況下是”JVM Heap Size * 0.2 * 0.8 =?JVM Heap Size * 0.16”。需要注意的是,當你多個線程同時在一個executor中運行時(spark.executor.cores/spark.task.cpus 大于1的情況下),那么map output的每個task將會擁有 “JVM Heap Size * spark.shuffle.memoryFraction * spark.shuffle.safetyFraction?/?spark.executor.cores * spark.task.cpus。
使用此種模式,會比使用hashing要慢一點,可通過bypassMergeThreshold找到集群的最快平衡點。
Tungsten Sort 輸出中間數(shù)據(jù)
使用此種排序方法的優(yōu)點在于,操作的二進制數(shù)據(jù)不需要進行反序列化。它使用 sun.misc.Unsafe模式進行直接數(shù)據(jù)的復制,因為沒有反序列化,所以直接是個字節(jié)數(shù)組。同時,它使用特殊的高效緩存器ShuffleExtemalSorter壓記錄與指針以及排序的分區(qū)id.只用了8 Bytes的空間的排序數(shù)組。這將會比使用CPU緩存要效率。
每個spill的數(shù)據(jù)、指針進行排序,輸出到一個索引文件中。隨后將這些partitions再次合并到一個輸出文件中。
#refer:https://0x0fff.com/spark-memory-management/
#