Spark的兩種核心Shuffle(HashShuffle與SortShuffle)的工作流程與源碼分析(手把手看源碼)

寫在前面的話:本篇博客爲原創,認真閱讀需要比對spark 2.1.1的源碼,預計閱讀耗時30分鐘,如果大家發現有問題或者是不懂的,歡迎討論
歡迎關注公衆號:後來X

spark 2.1.1的源碼包(有需要自取):關注公衆號【後來X】,回覆spark源碼

在spark中說到shuffle,大家應該不陌生,因爲有shuffle所以才把stage分爲

  • ShuffleMapStage:前面的所有stage
  • finalStage:最後一個stage
    在這裏插入圖片描述
    今天主要說的是Spark的兩種核心Shuffle

1、HashShuffle(2.0版本已淘汰)

在這裏插入圖片描述
在這裏插入圖片描述
通過上述的兩張圖,大家就能夠看出來,優化後的hashShuffle明顯比優化前少了很多中間的小文件,而原因是:Task複用Buffer緩衝區(也稱爲合併機制)
優化的HashShuffle開啓合併機制的配置是spark.shuffle.consolidateFiles,設置爲true即可。

2、SortShuffle

在這裏插入圖片描述
過程解析:

  1. 先把數據直接寫入內存
  2. 判斷是否達到閾值,如果達到就會將內存數據結構的數據寫入到磁盤
  3. 在溢寫磁盤前,先根據key進行排序,排序過後的數據,會分批寫入到磁盤文件中。默認批次爲10000條,也就是10000條寫一次
  4. 寫入磁盤文件通過緩衝區溢寫的方式,每次溢寫都會產生一個磁盤文件,也就是說一個Task過程會產生多個臨時文件。
  5. 最後在每個Task中,將所有的臨時文件合併爲一個文件,這就是merge過程,同時單獨寫一份索引文件,標識下游各個Task的數據在文件中的索引,start offset和end offset。

這其中的有一個關鍵點:達到閾值,這裏的這個閾值不像之前在hadoop中的shuffle緩衝區是固定的100M,這裏的緩衝區是可變的(具體看源碼分析第15步

bypass SortShuffle

在這裏插入圖片描述
特點:

  1. 不排序(優於SortShuffle)
  2. 最後合併文件(優於HashShuffle)

bypass運行機制的觸發條件如下:

  1. shuffle reduce task數量小於spark.shuffle.sort.bypassMergeThreshold參數的值,默認爲200。(減少中間小文件產生)
  2. 不是聚合類的shuffle算子(比如reduceByKey)

那我們接下來一起看看sortShuffle的源碼,來加深我們對於spark SortShuffle的理解

首先這個shuffle過程肯定是發生在ShuffleMapStage最後一步執行任務的階段,所以我們呢從Executor端執行任務的入口開始看:

  1. 雙擊Shift,找到Executor,並且通過ctrl + F找到執行任務的run()方法
    在這裏插入圖片描述
  2. 前面都是變量賦值,我們找到真正的執行task的run方法
    在這裏插入圖片描述
    在這裏插入圖片描述
  3. 從run()進入,發現還是在判斷線程是否死掉,繼續找到runTask()方法
    在這裏插入圖片描述
  4. 進入後發現需要找實現類,ctrl + h ,找到ShuffleMapTask實現類
    在這裏插入圖片描述
    在這裏插入圖片描述
  5. 找到實現類後,還是繼續找真正的執行,runTask()方法,發現直接就是開始Write寫數據,而讀數據是執行括號內的iterator()方法,那麼我們先了解一下是怎麼讀數據的,要了解怎麼寫數據直接跳到12步
    在這裏插入圖片描述
  6. 進入iterator()方法,最終找到compute方法,這個方法是特別關鍵的,是整個運算的核心,我們找到實現類的compute()方法
    在這裏插入圖片描述
    在這裏插入圖片描述
    在這裏插入圖片描述
    在這裏插入圖片描述
    我們注意到在最終這個compute方法張,有真正的read()方法,來讀取數據
  7. 到現在爲止,讀數據的流程我們已經清楚了,接下來我們返回來第5步開始繼續看寫數據的流程,我們先了解一下這個依賴的關係
    在這裏插入圖片描述
    在這裏插入圖片描述
    在這裏插入圖片描述
  8. 通過上面的實現類,找到註冊shuffle的實現方法,這一步非常關鍵,在這一步判斷是否能使用bypass SortShuffle
    在這裏插入圖片描述
  9. 進入if條件,根據2個條件是否都滿足來決定返回值爲true還是false,來決定能否使用bypass SortShuffle
    在這裏插入圖片描述
  10. 到這裏我們已經看到了bypass運行機制的2個觸發條件,我們現在返回第5步從getWrite()方法進入,找到實現類中的實現方法在這裏插入圖片描述
    在這裏插入圖片描述
    在這裏插入圖片描述
    到這一步的時候已經能夠前後對應起來了,上面這些步驟最終知道了最後才採用哪種shuffle方式,那麼接下來我們繼續接着看具體的shuffle過程有什麼區別,我麼主要關心SortShuffle和bypassSortShuffle
  11. 還是從第5步中的write方法進入,先看實現類SortShuffle
    在這裏插入圖片描述
    在這裏插入圖片描述
  12. 判斷是否需要排序,並把數據全部插入內存
    在這裏插入圖片描述
  13. 我們先從insetAll()方法進入,看一下數據是如何寫出的
    在這裏插入圖片描述
  14. 判斷是否需要溢寫
    在這裏插入圖片描述
    在這裏插入圖片描述
  15. 當上面的步驟最後需要溢寫的時候就開始找真正溢寫的方法,發現最溢寫出的文件組成了一個集合
    在這裏插入圖片描述
    在這裏插入圖片描述
    在這裏插入圖片描述
  16. 現在溢寫出道一個集合中後,我們看一下merge方法,參數就是一個集合,對應上一步的溢寫。
    在這裏插入圖片描述
    上面就是SortShuffle的全過程,那麼接下來繼續看bypassSortShuffle
  17. 返回第12步中,我們選擇bypassSortShuffle,找到對應的write方法,按照分區進行溢寫(因爲也不需要排序),最後把溢寫出的文件合併爲一個文件
    在這裏插入圖片描述
    在這裏插入圖片描述
    以上就是bypassSortShuffle的溢寫過程。

最後總結一下:

hashShuffle:

  • 優化前:每個task將數據按照分區器(hash/numreduce取模)計算,寫入buffer
    最後的小文件個數爲: Task數量 * 文件類型(分區器的計算結果的種類個數)
  • 優化後:只是多個task共用buffer緩衝區,
    最後的小文件個數爲: CPU核數 * 文件類型(分區器的計算結果的種類個數)

普通的SortShuffle:

合併爲1個文件+1個索引文件,中間先排序後溢寫

bypassSortShuffle:

  • 不排序,不能使用預聚合算子(比如reduceByKey)
  • reduce task數量小於200(爲了減少中間小文件的產生)

好啦,今天就說到這裏啦,下一篇再見。

最後再來一波,喜歡我的歡迎點贊,歡迎關注我的公衆號:後來X,回覆:spark源碼,獲取spark2.1.1源碼包以及大數據資料

持續更新,未完待續!

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