Hadoop/Spark的shuffle面試題

由於shuffle階段涉及磁盤的讀寫和網絡IO,因此shuffle性能的高低直接影響整個程序的性能和吞吐量。 

1. spark的shuffle 是什麼?過程? 怎麼調優?
在MapReduce過程中需要將各個節點上的同一類數據彙集到一個節點進行計算。把這些分佈在不同節點的數據按照一定規則聚集到一起的過程,就稱之爲shuffle(Shuffle是Map和Reduce之間的操作,Shuffle 過程本質上就是將 Map 端獲得的數據使用分區器進行劃分,並將數據發送給對應的 Reducer 的過程)。

在Spark中,只有寬依賴纔會進行數據shuffle【所以也稱之爲ShuffleDependency,根據寬依賴劃分Stage】,窄依賴不會進行數據shuffle【稱之爲NarrowDependency,只會劃分一個Stage】。從容災的角度來說,寬依賴需要恢復所有的父RDD。

對於reduce來說,處理函數的輸入是key相同的所有value,但是這些value所在的數據集(即map的輸出)位於不同的節點上,因此需要對map的輸出進行重新組織,使得同樣的key進入相同的reducer。 shuffle移動了大量的數據,對計算、內存、網絡和磁盤都有巨大的消耗,因此,只有確實需要shuffle的地方纔應該進行shuffle。 
過程如圖:【注:其實過程遠比這個圖複雜很多很多,這只是極簡的圖】 
想做的解釋都放圖中了,就不單獨贅述了。 


spark2.0及之後的版本中只存在SortShuffleManager而將原來的HashShuffleManager廢棄掉(在1.2版本之前的Shuffle方法);

開啓了consolidation機制後,每個節點上Map write磁盤文件數量爲:num(core)*num(redece task); 
調優 
i):針對應用對內存緩存大小進行調整——設置過大可能引發OOM;當緩存數據達到設置的閾值的時候就會往磁盤中寫入,所以設置過小的話,又會有過多的磁盤io操作。 
ii):TODO

2. hadoop的MapReduce以及shuffle過程,畫圖。怎麼調優?
主要參考:[6]

MapReduce中,Map是一個將輸入記錄轉換爲中間記錄的任務。已轉換的中間記錄不一定非得與輸入記錄具有相同的類型。一個給定的輸入對可能映射到零個或多個輸出對。Map數量通常由輸入數據的大小決定,也就是輸入數據的總的塊數【(MRJobConfig.NUM_MAPS可設置】。Reducer接收的數據是Mapper中處理出來的已經排好序的數據。 
在MapReduce過程中需要將各個節點上的同一類數據彙集到一個節點進行計算。把這些分佈在不同節點的數據按照一定規則聚集到一起的過程,就稱之爲shuffle。 
如圖: 
 
1、向client端提交MapReduce job.

2、隨後yarn的ResourceManager對可用資源進行分配.

3、由NodeManager加載與監控containers.

4、通過applicationMaster與ResourceManager進行資源申請及狀態交互,由NodeManagers進行MapReduce運行時job的管理.

5、通過hdfs進行job配置文件、jar包的各節點分發。 
Map階段: 
i): map任務輸出結果到一個環狀的內存緩衝區,緩衝區的大小可通過修改配置項mpareduce.task.io.sort.mb進行修改,當寫入內存的大小到達一定比例,默認爲80%(可通過mapreduce.map.sort.spill.percent配置項修改),便開始寫入磁盤。 
ii): 這裏將map輸出的結果進行壓縮會大大減少磁盤IO與網絡傳輸的開銷(配置參數mapreduce.map .output.compress 設置爲true,如果使用第三方壓縮jar,可通過mapreduce.map.output.compress.codec進行設置) 
iii): 隨後這些paritions輸出文件將會通過HTTP發送至reducers,傳送的最大啓動線程通過mapreduce.shuffle.max.threads進行配置。 
Reduce階段: 
i): 首先上面每個節點的map都將結果寫入了本地磁盤中,現在reduce需要將map的結果通過集羣拉取過來,這裏要注意的是,需要等到所有map任務結束後,reduce纔會對map的結果進行拷貝,由於reduce函數有少數幾個複製線程,以至於它可以同時拉取多個map的輸出結果。默認的爲5個線程(可通過修改配置mapreduce.reduce.shuffle.parallelcopies來修改其個數) 
reducers怎麼知道從哪些機器拉取數據呢? 
ii):  當所有map的任務結束後,applicationMaster通過心跳機制(heartbeat mechanism),由心跳機制獲知mapping的輸出結果與機器host,所以reducer會定時的通過一個線程訪問applicationmaster請求map的輸出結果。 
iii):  Map的結果將會被拷貝到reduce task的JVM的內存中(內存大小可在mapreduce.reduce.shuffle.input.buffer.percent中設置)如果不夠用,則會寫入磁盤。當內存緩衝區的大小到達一定比例時(可通過mapreduce.reduce.shuffle.merge.percent設置)或map的輸出結果文件過多時(可通過配置mapreduce.reduce.merge.inmen.threshold),將會合並(merged)隨之寫入磁盤。 
iv):  要注意,所有的map結果這時都是被壓縮過的,需要先在內存中進行解壓縮,以便後續合併它們。(合併最終文件的數量可通過mapreduce.task.io.sort.factor進行配置) 最終reduce進行運算進行輸出。 
   
調優,TODO:

3. spark的shuffle和Hadoop的shuffle(mapreduce)的聯繫和區別是什麼
聯繫:兩者都是將 mapper(Spark 裏是 ShuffleMapTask)的輸出進行 partition,不同的 partition 送到不同的 reducer(Spark 裏 reducer 可能是DAG中下一個 stage 裏的 ShuffleMapTask,也可能是 ResultTask)。Reducer 以內存作緩衝區,邊 shuffle 邊 aggregate 數據,等到數據 aggregate 好以後進行 reduce() (Spark 裏可能是後續的一系列操作)。Spark中的很多計算是作爲MapReduce計算框架的一種優化實現。

區別在於: 
從底層來看:最開始Spark儘量避免Hadoop多餘的排序(MapReduce 爲了方便對存在於不同 partition 的 key/value Records進行Group,就提前對 key 進行排序,這樣做的好處在於 combine/reduce() 可以處理大規模的數據。Spark 認爲很多應用不需要對 key 排序,所以Spark提供了基於hash的Shuffle寫,通常使用 HashMap 來對 shuffle 來的數據進行 aggregate,這種方法不會對數據進行提前排序,並且在1.2版本之前,這種方法是默認方法——但是現在的默認方法還是基於排序的Shuffle,並且在在spark2.0及之後的版本中只存在SortShuffleManager而將原來的HashShuffleManager廢棄掉(但是shuffleWriter的子類BypassMergeSortShuffleWriter和已經被廢棄掉的HashShuffleWriter類似))[1][4] 
從實現角度來看: Hadoop MapReduce 將處理流程劃分出明顯的幾個階段:map(), spill, merge, shuffle, sort, reduce() 等。每個階段各司其職,可以按照過程式的編程思想來逐一實現每個階段的功能。在 Spark 中,沒有這樣功能明確的階段,只有不同的 stage 和一系列的 transformation操作,所以 spill, merge, aggregate 等操作需要蘊含在一些transformation操作中。 
從數據流角度:Mapr只能從一個map stage接受數據,Spark 可以從多個 Map Stages shuffle 數據(這是 DAG 型數據流中寬依賴的優勢,可以表達複雜的數據流操作)。 
從數據粒度角度:Spark 粒度更細,可以更即時的將獲取到的 record 與 HashMap 中相同 key 的 records 進行合併。 
從性能優化角度來講:Spark考慮的更全面。Spark 針對不同類型的操作、不同類型的參數,會使用不同的 shuffle write 方式。比如 Shuffle write 有三種實現方式 [2] 
 
但是hadoop的2.7.1開始,新增了一個加密shuffle:加密Shuffle功能允許使用HTTPS和可選的客戶端身份驗證(也稱爲雙向HTTPS或帶客戶端證書的HTTPS)對MapReduce shuffle進行加密 [3]

4. 哪些算子涉及到shuffle操作
sortByKey、groupByKey、reduceByKey、countByKey、join、cogroup等聚合操作。

擴展:[5] 
(1)MapReduce Shuffle發展史 
【階段1】:MapReduce Shuffle的發展也並不是一馬平川的,剛開始(0.10.0版本之前)採用了“每個Map Task產生R個文件”的方案,前面提到,該方案會產生大量的隨機讀寫IO,對於大數據處理而言,非常不利。 
【階段2】:爲了避免Map Task產生大量文件,HADOOP-331嘗試對該方案進行優化,優化方法:爲每個Map Task提供一個環形buffer,一旦buffer滿了後,則將內存數據spill到磁盤上(外加一個索引文件,保存每個partition的偏移量),最終合併產生的這些spill文件,同時創建一個索引文件,保存每個partition的偏移量。 
(階段2):這個階段並沒有對shuffle架構做調成,只是對shuffle的環形buffer進行了優化。在Hadoop 2.0版本之前,對MapReduce作業進行參數調優時,Map階段的buffer調優非常複雜的,涉及到多個參數,這是由於buffer被切分成兩部分使用:一部分保存索引(比如parition、key和value偏移量和長度),一部分保存實際的數據,這兩段buffer均會影響spill文件數目,因此,需要根據數據特點對多個參數進行調優,非常繁瑣。而MAPREDUCE-64則解決了該問題,該方案讓索引和數據共享一個環形緩衝區,不再將其分成兩部分獨立使用,這樣只需設置一個參數控制spill頻率。 
【階段3(進行中)】:目前shuffle被當做一個子階段被嵌到Reduce階段中的。由於MapReduce模型中,Map Task和Reduce Task可以同時運行,因此一個作業前期啓動的Reduce Task將一直處於shuffle階段,直到所有Map Task運行完成,而在這個過程中,Reduce Task佔用着資源,但這部分資源利用率非常低,基本上只使用了IO資源。爲了提高資源利用率,一種非常好的方法是將shuffle從Reduce階段中獨立處理,變成一個獨立的階段/服務,由專門的shuffler service負責數據拷貝,目前百度已經實現了該功能(準備開源?),且收益明顯,具體參考:MAPREDUCE-2354。 
(2) Spark Shuffle發展史 
目前看來,Spark Shuffle的發展史與MapReduce發展史非常類似。初期Spark在Map階段採用了“每個Map Task產生R個文件”的方法,在Reduce階段採用了map分組方法,但隨Spark變得流行,用戶逐漸發現這種方案在處理大數據時存在嚴重瓶頸問題,因此嘗試對Spark進行優化和改進,相關鏈接有:External Sorting for Aggregator and CoGroupedRDDs,“Optimizing Shuffle Performance in Spark”,“Consolidating Shuffle Files in Spark”,優化動機和思路與MapReduce非常類似。 
Spark在前期設計中過多依賴於內存,使得一些運行在MapReduce之上的大作業難以直接運行在Spark之上(可能遇到OOM問題)。目前Spark在處理大數據集方面尚不完善,用戶需根據作業特點選擇性的將一部分作業遷移到Spark上,而不是整體遷移。隨着Spark的完善,很多內部關鍵模塊的設計思路將變得與MapReduce升級版Tez非常類似。
 

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