shuffle屬於不斷被優化和改進的代碼庫,是MapReduce的“心臟”。
shuffle可以將其定義爲:map的輸出到reduce的輸入(在一些語境中,代表reduce接受map輸出的這部分)
map端
我們知道map產生的輸出是臨時寫到本地磁盤的,但是他並不是簡單的寫到本地磁盤中,這個過程更爲複雜,如圖:
他會首先使用緩衝的方式寫入到內存中,並且處於效率的考慮進行預排序。每個map都有一個緩衝區用於存儲任務輸出,這個緩衝區的大小默認爲100MB,可以通過io.sort.mb屬性調整。一旦緩衝的內容達到預設的閥值(通過io.sort.spill.percent,默認是0.8或80%),一個後臺進程便把內容溢出(spill)到磁盤。在溢出過程map輸出會繼續寫到緩衝區,如果在此期間被填滿,則發生堵塞,直到寫磁盤過程完成。這個溢出寫的過程會將數據寫到mapreduce.cluster.local.dir指定的目錄內。在寫硬盤之前會根據輸出的reduce進行分區(partition),然後對每個分區內容進行排序,如果有combiner函數,則在排序之後執行combiner。每次內存緩衝區達到溢出的閥值,就會新建一個溢出文件(spill file)。最終會有幾個溢出文件,這些溢出文件會被合併成一個已分區且已排序的輸出文件,io.sort.factor控制一次最多能夠合併多少流,默認是10。如果至少有3個溢出文件(這個值由min.num.spills.for.combine屬性設置)則就會在輸出文件寫到磁盤之前在此運行以此combiner。將輸出進行壓縮可以減少輸出及傳遞到reduce的網絡開銷,可以設置mapreduce.compress.map.output設置爲true,使用mapreduce.map.output.compression.codec指定壓縮方法。
reduce端
reduce任務需要集羣中若干個map的輸出作爲其輸入,但是每個map的完成時間並不一樣,所以只要有一個map輸出,reduce就開始複製其輸出,這就是reduce端的複製階段。reduce有少量的複製線程,默認是5個,這個值由mapreduce.reduce.parallel.copies屬性改變。
那麼reduce如何知道從哪臺機器獲取map輸出呢?
map任務完成後,會通知其父tasktracker,tasktracker會通知jobtracker(在MR2中是applicationMaster),從而jobtracker(applicationMaster)知道了tasktracker與map的映射關係,reduce中的一個線程會定期向applicationMaster(或者jobtracker)進行詢問,以便獲取map輸出的位置。
複製完成後,reduce開始進入排序階段(其實是合併節階段,因爲排序是在map端進行的),這個階段合併map輸出,保持其排好的順序。這個合併是循環進行的,可以設置合併因子io.sort.factor,默認是10,即每趟合併10個文件,假設總共50個map,總共進行5趟,最終有5箇中文文件。之後是reduce階段,直接把數據輸入到reduce函數,而不用將這5個文件合併稱一個大文件。reduce函數輸出直接寫到HDFS上。
配置調優
map端的調優屬性:
屬性名稱 | 類型 | 默認值 | 說明 |
io.sort.mb | int | 100 | map輸出所使用的內存緩衝區大小,以MB爲單位 |
io.sort.spill.percent | float | 0.80 | 緩衝區預設的閥值,超過這個百分比開始將內容溢到磁盤 |
io.sort.factory | int | 10 | 排序文件時一次最多合併的流數,在reduce端也是用 |
min.num.spills.for.combine | int | 3 | 運行combiner所需要最少溢出文件數 |
mapreduce.compress.map.output | Boolean | false | 壓縮map輸出 |
mapreduce.map.output.compression.codec | Class Name | org.apache.hadoop. io.compress.DefaultCodec |
用於map輸出的壓縮編碼器 |
tasktracker.http.threads | int | 40 | 每個tasktracker運行的線程數,用於將map輸出到reduce,在YARN不適用 |
這個過程總的來說就是要爲shuffle分配更多的內存,但是這時候可能還需要考慮到map函數和reduce函數能夠得到足夠運行的內。所以一般map函數和reduce函數在編寫的時候儘量少佔內存。map端可以通過避免多次溢出寫磁盤來獲得最佳性能,一次是最佳的情況。
reduce端的調優屬性
屬性名稱 | 類型 | 默認值 | 說明 |
mapreduce.reduce.parallel.copies | int | 5 | 用於把map的輸出複製到reduce的線程數 |
mapreduce.reduce.copy.backoff | int | 300 | 在聲明失敗之前,reducer獲取一個map輸出所花的最大時間,以秒 爲單位,如果失敗,reducer可以在此時間內嘗試重傳 |
io.sort.factor | int | 10 | 排序合併時的合併因子 |
mapreduce.job.shuffle.input.buffer.percent | float | 0.70 | 在shuffle階段,分配給map輸出的緩衝區佔堆空間的百分比 |
mapreduce.iob.shuffle.merge.percent | float | 0.66 | map輸出緩衝區(上面定義的那個)的閥值使用比例,用於啓動合併輸出和磁盤溢出寫的過程 |
mapreduce.inmem.merge.threshold | int | 1000 | 啓動合併輸出和磁盤溢出寫過程的map的輸出的閥值數。0或更小,意味着沒有閥值限制 |
mapreduce.iob.reduce.input.buffer.percent | float | 0.0 | 在reduce過程,在內存中保存map輸出的空間佔整個堆空間的比例。reduce階段開始時 ,內存中的map輸出不能大於這個值 |