MapReduce過程、Spark和Hadoop以Shuffle爲中心的對比分析

mapreduce與Spark的map-Shuffle-reduce過程

  • mapreduce過程解析(mapreduce採用的是sort-based shuffle)
    • 將獲取到的數據分片partition進行解析,獲得k/v對,之後交由map()進行處理.
    • map函數處理完成之後,進入collect階段,對處理後的k/v對進行收集,存儲在內存的環形緩衝區中。
    • 當環形緩衝區中的數據達到閥值之後(也可能一直沒有達到閥值,也一樣要將內存中的數據寫入磁盤),將內存緩衝區中的數據通過SpillThread線程轉移到磁盤上。需要注意的是,轉移之前,首先利用快排對記錄數據進行排序(原則是先按照分區編號,再按照key進行排序,注意,排序是在寫入磁盤之前的)。之後按照partition編號,獲取上述排序之後的數據並將其寫入Spill.out文件中(一個Spill.out文件中可能會有多個分區的數據--因爲一次map操作會有多次的spill的過程),需要注意的是,如果人爲設置了combiner,在寫入文件之前,需要對每個分區中的數據進行聚集操作。該文件同時又對應SpillRecord結構(Spill.out文件索引)。
    • map的最後一個階段是merge:該過程會將每一個Spill.out文件合併成爲一個大文件(該文件也有對應的索引文件),合併的過程很簡單,就是將多個Spill.out文件的在同一個partition的數據進行合併。(第一次聚合)

    • shuffle階段。首先要說明的是shuffle階段有兩種閥值設置。第一,獲取來自map的結果數據的時候,根據數據大小(file.out的大小)自然劃分到內存或者是磁盤(這種閥值的設置跟map階段完全不同);第二,內存和磁盤能夠保存的文件數目有閥值,超出閥值,會對文件進行merge操作,即小文件合併成爲大文件。Shuffle過程:
      1)獲取完成的Map Task列表。
      2)進行數據的遠程拷貝(http get的方法),根據數據文件的大小自然劃分到內存或者是磁盤。
      3)當內存或者磁盤的文件較多時,進行文件合併。(第二次聚合)

    • reduce之前需要進行Sort操作,但是兩個階段是並行化的,Sort在內存或者磁盤中建立小頂堆,並保存了指向該小頂堆根節點的迭代器,同時Reduce Task通過迭代器將key相同的數據順次講給reduce()函數進行處理。
  • Spark Shuffle過程解析(採用hash-based shuffle)
    • RDD是Spark與Hadoop之間最明顯的差別(數據結構),Spark中的RDD具有很多特性,在這裏就不再贅述。
    • Spark與Hadoop之間的Shuffle過程大致類似,Spark的Shuffle的前後也各有一次聚合操作。但是也有很明顯的差別:Hadoop的shuffle過程是明顯的幾個階段:map(),spill,merge,shuffle,sort,reduce()等,是按照流程順次執行的,屬於push類型;但是,Spark不一樣,因爲Spark的Shuffle過程是算子驅動的,具有懶執行的特點,屬於pull類型。
    • Spark與Hadoop的Shuffle之間第二個明顯的差別是,Spark的Shuffle是hash-based類型的,而Hadoop的Shuffle是sort-based類型的。下面簡介一下Spark的Shuffle:
      1.正因爲是算子驅動的,Spark的Shuffle主要是兩個階段:Shuffle Write和Shuffle Read。
      2.ShuffleMapTask的整個的執行過程就是Shuffle Write階段
      3.Sprk的Shuffle過程剛開始的操作就是將map的結果文件中的數據記錄送到對應的bucket裏面(緩衝區),分到哪一個bucket根據key來決定(該過程是hash的過程,每一個bucket都對應最終的reducer,也就是說在hash-based下,數據會自動劃分到對應reducer的bucket裏面)。之後,每個bucket裏面的數據會不斷被寫到本地磁盤上,形成一個ShuffleBlockFile,或者簡稱FileSegment。上述就是整個ShuffleMapTask過程。之後,reducer會去fetch屬於自己的FileSegment,進入shuffle read階段。
      4.需要注意的是reducer進行數據的fetch操作是等到所有的ShuffleMapTask執行完纔開始進行的,因爲所有的ShuffleMapTask可能不在同一個stage裏面,而stage執行後提交是要在父stage執行提交之後才能進行的,所以fetch操作並不是FileSegment產生就執行的。
      5.需要注意的是,剛fetch來的FileSegment存放在softBuffer緩衝區,Spark規定這個緩衝界限不能超過spark.reducer.maxMbInFlight,這裏用softBuffer表示,默認大小48MB。
      6.經過reduce處理後的數據放在內存+磁盤上(採用相關策略進行spill)。
      7.fetch一旦開始,就會邊fetch邊處理(reduce)。MapReduce shuffle階段就是邊fetch邊使用combine()進行處理,但是combine()處理的是部分數據。MapReduce不能做到邊fetch邊reduce處理,因爲MapReduce爲了讓進入reduce()的records有序,必須等到全部數據都shuffle-sort後再開始reduce()。然而,Spark不要求shuffle後的數據全局有序,因此沒必要等到全部數據shuffle完成後再處理。爲了實現邊shuffle邊處理,而且流入的records是無序的可以用aggregate的數據結構,比如HashMap。

  • hash-based 和 sort-based的對比

    • hash-based故名思義也就是在Shuffle的過程中寫數據時不做排序操作,只是將數據根據Hash的結果,將各個Reduce分區的數據寫到各自的磁盤文件中。這樣帶來的問題就是如果Reduce分區的數量比較大的話,將會產生大量的磁盤文件(Map*Reduce)。如果文件數量特別巨大,對文件讀寫的性能會帶來比較大的影響,此外由於同時打開的文件句柄數量衆多,序列化,以及壓縮等操作需要分配的臨時內存空間也可能會迅速膨脹到無法接受的地步,對內存的使用和GC帶來很大的壓力,在Executor內存比較小的情況下尤爲突出,例如Spark on Yarn模式。但是這種方式也是有改善的方法的:

      在一個core上連續執行的ShuffleMapTasks可以共用一個輸出文件ShuffleFile。先執行完的ShuffleMapTask形成ShuffleBlock i,後執行的ShuffleMapTask可以將輸出數據直接追加到ShuffleBlock i後面,形成ShuffleBlock i’,每個ShuffleBlock被稱爲FileSegment。下一個stage的reducer只需要fetch整個ShuffleFile就行了。這樣的話,整個shuffle文件的數目就變爲C*R了。

    • sort-based是Spark1.1版本之後實現的一個試驗性(也就是一些功能和接口還在開發演變中)的ShuffleManager,它在寫入分區數據的時候,首先會根據實際情況對數據採用不同的方式進行排序操作,底線是至少按照Reduce分區Partition進行排序,這樣來至於同一個Map任務Shuffle到不同的Reduce分區中去的所有數據都可以寫入到同一個外部磁盤文件中去,用簡單的Offset標誌不同Reduce分區的數據在這個文件中的偏移量。這樣一個Map任務就只需要生成一個shuffle文件,從而避免了上述HashShuffleManager可能遇到的文件數量巨大的問題。上述過程與mapreduce的過程類似。

  • 兩者的性能比較,取決於內存,排序,文件操作等因素的綜合影響。

    • 對於不需要進行排序的Shuffle操作來說,如repartition等,如果文件數量不是特別巨大,HashShuffleManager面臨的內存問題不大,而SortShuffleManager需要額外的根據Partition進行排序,顯然HashShuffleManager的效率會更高。
    • 而對於本來就需要在Map端進行排序的Shuffle操作來說,如ReduceByKey等,使用HashShuffleManager雖然在寫數據時不排序,但在其它的步驟中仍然需要排序,而SortShuffleManager則可以將寫數據和排序兩個工作合併在一起執行,因此即使不考慮HashShuffleManager的內存使用問題,SortShuffleManager依舊可能更快。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章