Spark_Spark中的幾種Shuffle 以及工作原理, 含HashShuffle

Base Spark 2.0 +

 

參考文章

1.spark基礎之shuffle機制和原理分析

https://blog.csdn.net/zhanglh046/article/details/78360762

2.Spark Shuffle原理、Shuffle操作問題解決和參數調優

https://www.cnblogs.com/arachis/p/Spark_Shuffle.html

 

一 概述

   Shuffle就是對數據進行重組,由於分佈式計算的特性和要求,在實現細節上更加繁瑣和複雜

   

Hadoop Shuffle

詳細介紹文章 : https://blog.csdn.net/u010003835/article/details/105334025

     在MapReduce框架,Shuffle是連接Map和Reduce之間的橋樑,Map階段通過shuffle讀取數據並輸出到對應的Reduce;而Reduce階段負責從Map端拉取數據並進行計算。在整個shuffle過程中,往往伴隨着大量的磁盤和網絡I/O。所以shuffle性能的高低也直接決定了整個程序的性能高低。


 

 

Spark Shuffle

    Spark也會有自己的shuffle實現過程, 如下圖所示

 

      在DAG調度的過程中,Stage階段的劃分是根據是否有shuffle過程,也就是存在ShuffleDependency寬依賴的時候,需要進行shuffle,這時候會將作業job劃分成多個Stage;

    並且在劃分Stage的時候,構建ShuffleDependency的時候進行shuffle註冊,獲取後續數據讀取所需要的ShuffleHandle,最終每一個job提交後都會生成一個ResultStage和若干個ShuffleMapStage,其中ResultStage表示生成作業的最終結果所在的Stage. 

     ResultStage與ShuffleMapStage中的task分別對應着ResultTask與ShuffleMapTask。

     一個作業,除了最終的ResultStage外,其他若干ShuffleMapStage中各個ShuffleMapTask都需要將最終的數據根據相應的Partitioner對數據進行分組,然後持久化分區的數據。
 

 

Spark 中的Shuffle 模型

目前Spark 中的Shuffle 只有SortShuffle 一種, 在Spark 2.0 之前存在 3中Shuffle 邏輯,但是 Spark 2 之後將兩外另種移除了。

Spark 1.* + 的存在 hash、sort 和 tungtsten-sort, tungtsten-sort  與 sort 貌似進行了合併。

http://spark.apache.org/docs/1.6.0/configuration.html

這是1.6的文檔

spark.shuffle.manager sort Implementation to use for shuffling data. There are two implementations available: sort and hash. Sort-based shuffle is more memory-efficient and is the default option starting in 1.2.

 

 

Spark 中的 Based Sort Shuffle 模型

    因爲Spark 2.0 + 已經不存在 HashShuffle 選項了,我們先看下 Sort-Based Shuffle 介紹。

 

 

Sort-Based Shuffle 詳解


     爲了緩解Shuffle過程產生文件數過多和Writer緩存開銷過大的問題,spark引入了類似於hadoop Map-Reduce的shuffle機制。該機制每一個ShuffleMapTask不會爲後續的任務創建單獨的文件,而是會將所有的Task結果寫入同一個文件,並且對應生成一個索引文件。

      以前的數據是放在內存緩存中,等到數據完了再刷到磁盤,現在爲了減少內存的使用,在內存不夠用的時候,可以將輸出溢寫到磁盤,結束的時候,再將這些不同的文件聯合內存的數據一起進行歸併,從而減少內存的使用量。一方面文件數量顯著減少,另一方面減少Writer緩存所佔用的內存大小,而且同時避免GC的風險和頻率。
 

 

 

Sort-Based Shuffle有幾種不同的策略:

  • BypassMergeSortShuffleWriter
  • SortShuffleWriter
  • UnasfeSortShuffleWriter

Spark 2.2.0 的截圖

 

BypassMergeSortShuffleWriter

運行流程

     此時task會爲每個下游task都創建一個臨時磁盤文件並將數據按key進行hash然後根據key的hash值,將key寫入對應的磁盤文件之中。當然,寫入磁盤文件時也是先寫入內存緩衝,緩衝寫滿之後再溢寫到磁盤文件的。最後,同樣會將所有臨時磁盤文件都合併成一個磁盤文件,並創建一個單獨的索引文件。

    該過程的磁盤寫機制其實跟未經優化的HashShuffleManager是一模一樣的,因爲都要創建數量驚人的磁盤文件,只是在最後會做一個磁盤文件的合併而已。因此少量的最終磁盤文件,也讓該機制相對未經優化的HashShuffleManager來說,shuffle read的性能會更好。

 

觸發機制

    因爲BypassMergeSortShuffleWriter這種方式比SortShuffleWriter更快,所以如果在Reducer數量不大,又不需要在map端聚合和排序,而且 Reducer的數目 <  spark.shuffle.sort.bypassMergeThrshold  (默認200)指定的閥值,就是用的是這種方式。

    不是排序類的shuffle算子(比如reduceByKey)。

 

選擇依據

    主要用於處理不需要排序和聚合的Shuffle操作,所以數據是直接寫入文件,數據量較大的時候,網絡I/O和內存負擔較重

    主要適合處理Reducer任務數量比較少的情況下

    將每一個分區寫入一個單獨的文件,最後將這些文件合併,減少文件數量;但是這種方式需要併發打開多個文件,對內存消耗比較大

 

 

SortShuffleWriter

 

 運行流程

    在溢寫到磁盤文件之前,會先根據key對內存數據結構中已有的數據進行排序。排序過後,會分批將數據寫入磁盤文件。默認的batch數量是10000條,也就是說,排序好的數據,會以每批1萬條數據的形式分批寫入磁盤文件。寫入磁盤文件是通過Java的BufferedOutputStream實現的。BufferedOutputStream是Java的緩衝輸出流,首先會將數據緩衝在內存中,當內存緩衝滿溢之後再一次寫入磁盤文件中,這樣可以減少磁盤IO次數,提升性能。

    一個task將所有數據寫入內存數據結構的過程中,會發生多次磁盤溢寫操作,也就會產生多個臨時文件。最後會將之前所有的臨時磁盤文件都進行合併,這就是merge過程,此時會將之前所有臨時磁盤文件中的數據讀取出來,然後依次寫入最終的磁盤文件之中。此外,由於一個task就只對應一個磁盤文件,也就意味着該task爲下游stage的task準備的數據都在這一個文件中,因此還會單獨寫一份索引文件,其中標識了下游各個task的數據在文件中的start offset與end offset。

   SortShuffleManager由於有一個磁盤文件merge的過程,因此大大減少了文件數量。

   比如第一個stage有50個task,總共有10個Executor,每個Executor執行5個task,而第二個stage有100個task。由於每個task最終只有一個磁盤文件,因此此時每個Executor上只有5個磁盤文件,所有Executor只有50個磁盤文件。

 

選擇依據

   比較適合數據量很大的場景或者集羣規模很大

   引入了外部排序器,可以支持在Map端進行本地聚合或者不聚合

   如果外部排序器enable了spill功能,如果內存不夠,可以先將輸出溢寫到本地磁盤,最後將內存結果和本地磁盤的溢寫文件進行合併

 

 

UnsafeShuffleWriter

   由於需要謹慎使用,我們暫不做分析

 

 

 

BypassMergeSortShuffleWriter 與 SortShuffleWriter 對比

  • 第一,磁盤寫機制不同;
  • 第二,不會進行排序。也就是說,啓用該機制的最大好處在於,shuffle write過程中,不需要進行數據的排序操作,也就節省掉了這部分的性能開銷。

 

 

Tips 

    另外這個Sort-Based Shuffle跟Executor核數沒有關係,即跟併發度沒有關係,它是每一個ShuffleMapTask都會產生一個data文件和index文件,所謂合併也只是將該ShuffleMapTask的各個partition對應的分區文件合併到data文件而已。所以這個就需要個Hash-BasedShuffle的consolidation機制區別開來。
 

 

 

 

Spark 中的Hash Shuffle (deprecated)

 

概述

     在spark-1.6版本之前,一直使用HashShuffle,在spark-1.6版本之後使用Sort-Base Shuffle,因爲HashShuffle存在的不足所以就替換了HashShuffle.

     我們知道,Spark的運行主要分爲2部分:一部分是驅動程序,其核心是SparkContext;另一部分是Worker節點上Task,它是運行實際任務的。程序運行的時候,Driver和Executor進程相互交互:運行什麼任務,即Driver會分配Task到Executor,Driver 跟 Executor 進行網絡傳輸; 任務數據從哪兒獲取,即Task要從 Driver 抓取其他上游的 Task 的數據結果,所以有這個過程中就不斷的產生網絡結果。其中,下一個 Stage 向上一個 Stage 要數據這個過程,我們就稱之爲 Shuffle。

    HashShuffle 存在兩個版本,未經優化的和已經優化的版本。

 

未經優化的 Hash Shuffle 

    在HashShuffle沒有優化之前,每一個ShufflleMapTask會爲每一個ReduceTask創建一個bucket緩存,並且會爲每一個bucket創建一個文件。這個bucket存放的數據就是經過Partitioner操作(默認是HashPartitioner)之後找到對應的bucket然後放進去,最後將數據 刷新bucket緩存的數據到磁盤上,即對應的block file.

   然後ShuffleMapTask將輸出作爲MapStatus發送到DAGScheduler的MapOutputTrackerMaster,每一個MapStatus包含了每一個ResultTask要拉取的數據的位置和大小

   ResultTask然後去利用BlockStoreShuffleFetcher向MapOutputTrackerMaster獲取MapStatus,看哪一份數據是屬於自己的,然後底層通過BlockManager將數據拉取過來

   拉取過來的數據會組成一個內部的ShuffleRDD,優先放入內存,內存不夠用則放入磁盤,然後ResulTask開始進行聚合,最後生成我們希望獲取的那個MapPartitionRDD
 

缺點:

    如上圖所示:在這裏有1個worker,2個executor,每一個executor運行2個ShuffleMapTask,有三個ReduceTask,所以總共就有4 * 3=12個bucket和12個block file。

   如果數據量較大,將會生成M*R個小文件,比如ShuffleMapTask有100個,ResultTask有100個,這就會產生100*100=10000個小文件。

  bucket緩存很重要,需要將ShuffleMapTask所有數據都寫入bucket,纔會刷到磁盤,那麼如果Map端數據過多,這就很容易造成內存溢出,儘管後面有優化,bucket寫入的數據達到刷新到磁盤的閥值之後,就會將數據一點一點的刷新到磁盤,但是這樣磁盤I/O就多了。

 

 

優化後的HashShuffle

 

   每一個Executor進程根據核數,決定Task的併發數量,比如executor核數是2,就是可以併發運行兩個task,如果是一個則只能運行一個task 。

   假設executor核數是1,ShuffleMapTask數量是M,那麼它依然會根據ResultTask的數量R,創建R個bucket緩存,然後對key進行hash,數據進入不同的bucket中,每一個bucket對應着一個block file,用於刷新bucket緩存裏的數據。然後下一個task運行的時候,那麼不會再創建新的bucket和block file,而是複用之前的task已經創建好的bucket和block file。即所謂同一個Executor進程裏所有Task都會把相同的key放入相同的bucket緩衝區中。

   這樣的話,生成文件的數量就是(本地worker的 executor數量  * executor的cores *  ResultTask數量 )如上圖所示,即2 * 1* 3 = 6個文件,每一個Executor的shuffleMapTask數量100,ReduceTask數量爲100,那麼

   未優化的HashShuffle的文件數是2 *1* 100*100 =20000,優化之後的數量是2*1*100 = 200文件,相當於少了100倍

 

缺點:

  如果 Reducer 端的並行任務或者是數據分片過多的話則 Core * Reducer Task 依舊過大,也會產生很多小文件。
 

 

HashShuffle 總結

       HashShuffle寫數據的時候,內存有一個bucket緩衝區,同時在本地磁盤有對應的本地文件,如果本地有文件,那麼在內存應該也有文件句柄也是需要耗費內存的。也就是說,從內存的角度考慮,即有一部分存儲數據,一部分管理文件句柄。如果Mapper分片數量爲1000,Reduce分片數量爲1000,那麼總共就需要1000000個小文件。所以就會有很多內存消耗,頻繁IO以及GC頻繁或者出現內存溢出。

      而且Reducer端讀取Map端數據時,Mapper有這麼多小文件,就需要打開很多網絡通道讀取,很容易造成Reducer(下一個stage)通過driver去拉取上一個stage數據的時候,說文件找不到,其實不是文件找不到而是程序不響應,因爲正在GC.

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