MapReduce
MapReduce簡介及優點
- MapReduce是一個分佈式運算程序的編程框架,是Hadoop數據分析的核心
- MapReduce的核心思想是將用戶編寫的邏輯代碼和架構中的各個組件整合成一個分佈式運算程序,實現一定程序的並行處理海量數據,提高效率
- 海量數據難以在單機上處理,而一旦將單機版程序擴展到集羣上進行分佈式運行勢必將大大增加程序的複雜程度,所以引入MapReduce架構,開發人員可以將精力集中於數據處理的核心業務邏輯上,而將分佈式程序中的公共功能封裝成框架,以降低開發的難度
- 一個完整的MapReduce 程序有三類實例進程
- MRAppMaster:負責整個程序的協調過程
- MapTask:負責map階段的數據處理
- ReduceTask:負責reduce階段的數據處理
MapReduce基本流程
第一步:InputFormat
第二步:Split
第三步:RecordReader
第四步:Map
第五步:Partition
第七步:Sort (Sort之前會進行Combiner)
第七步:Merge (Merge之前會進行Combiner)
第八步:Group
第九步:Reduce
第十步:OutputFormat
MapReduce整體詳細流程
1.Map階段
1、數據讀取、邏輯切片
首先,讀取數據的組件InputFormat(默認TextInputFormat)
會通過getSplits方法對輸入目錄中的文件進行邏輯切片規劃得到splits切片
有多少個split就對應啓動多少個MapTask,默認情況下split與block的對應關係默認是一對一
2、RecordReader處理輸出
將輸入文件切分爲splits之後,由RecordReader對象(默認LineRecordReader)進行讀取
以\n作爲分隔符,讀取一行數據,返回<key,value>
Key表示每行首字符偏移值,value表示這一行文本內容
3、執行map方法
讀取split返回<key,value>,進入用戶自己繼承的Mapper類中,執行用戶重寫的map函數
RecordReader讀取一行,用戶重寫的map調用一次,並輸出一個<key,value>
2.Shuffle階段(Map輸出之後,Reduce輸出之前)
1.數據分區、寫入內存
此時outputCollectot,會收集Map寫出的數據,將key/value以及Partition的結果將並放入內存中的環形緩衝區
注意:
a.在寫到環形緩衝區的過程中進行分區
b.緩衝區的作用是批量收集map結果,減少磁盤IO的影響
c.使用環形數據結構是爲了更有效地使用內存空間,在內存中放置儘可能多的數據;
d.環形緩衝區其實是一個字節數組,數組中存放着key、value的序列化數據和key、value的元數據信息,
包括partition、key的起始位置、value的起始位置以及value的長度;環形結構是一個抽象概念
e.緩衝區是有大小限制,默認是100MB,可修改;當Map task的輸出結果很多時,就可能會撐爆內存,
所以需要在一定條件下將緩衝區中的數據臨時寫入磁盤,然後重新利用這塊緩衝區;
這個從內存往磁盤寫數據的過程被稱爲Spill,中文可譯爲溢寫;這個溢寫是由單獨線程來完成,
不影響往緩衝區寫map結果的線程;溢寫觸發的比例默認是0.8,也就是當緩衝區的數據已經達到閾值時,
溢寫線程啓動,鎖定這80MB的內存,執行溢寫過程。Map task的輸出結果還可以往剩下的20MB內存中寫,互不影響
2、溢寫、排序、combine(局部合併)
當環形緩衝區中的數據到達一定比例時,開啓溢寫( 從內存往磁盤寫數據的過程被稱爲Spill,中文可譯爲溢寫 ),
鎖定需要溢寫的數據,將需要溢寫的數據寫到磁盤中的臨時文件中,
並在寫入前根據key進行 排序(sort)和合並(combine,可選操作)
注意:
a.分區(Partitioner)
MapReduce提供Partitioner接口,它的作用就是根據key或value及reduce的數量
來決定當前的這對輸出數據最終應該交由哪個Reducetask處理;
默認對key HashPartitioner 後再以 Reducetask 數量取模;
默認的取模方式只是爲了平均Reduce的處理能力,如果用戶自己對Partitioner有需求,可自行設置;
b.排序(key.compareTo):調用key的compareTo方法進行比較,默認是快排,字典排序
c.Combiner:作用是進行局部彙總;工作機制與Reduce完全一致;由用戶決定是否使用
d.數據區會有三個數據來記錄索引:順序,大小,偏移量等
e.當80%的數據區被寫滿之後,數據區會被鎖定,此時outputcollector收集的數據會寫入20%的保留區。
當數據區的數據溢寫完成後,鎖定解除。
f.此時的20%的保留區加上數據區的60%構成新的數據區,數據區剩餘的20%爲新的保留區
g.保留區與數據區的比例是可以由用戶決定的。
h.生成的每個文件叫做段(segment),每個文件還會有一個index索引文件(用於描述區的偏移量,
原始文件大小,壓縮後數據大小),該文件默認存儲在內存,內存空間不足夠時溢寫到磁盤
3.merge(整合)
當整個數據處理結束之後,會對磁盤中所有的臨時文件進行歸併,生成一個大文件
此時的歸併是將所有臨時文件中的相同分區的合併到一起,並對各個分區中的數據再進行一次排序(sort)
需要注意的問題:
a.一個Maptask最後會整合成一個大文件,大文件也會有索引
b.merge的過程中combiner也會起作用
c.reducetask會去每個Maptask上拉取對應分區的文件,默認存放在reduce端的環形緩衝區中(100M),
達到閾值溢寫磁盤,拉取完畢後,會得到一堆小文件,對所有的小文件,進行merge,歸併排序成一個大文件
3.Reduce階段
1.拉取、分組、合併數據
ReduceTask不斷從MapTask拉取屬於自己分區的數據讀取到內存中的緩存區裏,到達閾值後溢寫到磁盤中,
內存中的所有數據寫入完成之後開始做merge歸併,最後合併成一個分區相同的大文件,然後對這個
文件中的鍵值對按照key進行sort排序,排好序之後根據分區規則進行分組,分組完成後一組一組的調用reduce方法
2.執行reduce方法
MapReduce 調優
-
Map端的最高效率是:儘量減少環形緩衝區flush的次數(減少磁盤IO 的使用次數)
如何能夠減少環形緩衝區flush的次數:- 1、加大環形緩衝區的內存
- 2、增大緩衝區閾值的大小 (考慮剩餘的空間是不是夠系統使用)
- 3、對輸出的進行壓縮(壓縮-解壓的過程會消耗CPU)
-
Reduce端的最高效率是:儘量減少環形緩衝區flush的次數
- 1、儘量將所有的數據在內存中計算
-
在網絡帶寬、磁盤IO是瓶頸的前提下,能不使用IO 網絡就不使用,在必須使用的前提下,能少用就少用。
-
所有的、只要能夠減少網絡帶寬的開銷,只要能夠減少磁盤io的使用的次數的配置項,都是集羣調優的可選項;(可選項包括: 軟件層面【系統軟件和集羣軟件】,硬件層面,網絡層面)
MapReduce開發總結
mapreduce在編程的時候,基本上一個固化的模式,沒有太多可靈活改變的地方,除了以下幾處:
1)輸入數據接口:
InputFormat--->FileInputFormat(文件類型數據讀取的通用抽象類) DBInputFormat (數據庫數據讀取的通用抽象類)
默認使用的實現類是:TextInputFormat
job.setInputFormatClass(TextInputFormat.class)
TextInputFormat的功能邏輯是:一次讀一行文本,然後將該行的起始偏移量作爲key,行內容作爲value返回
2)邏輯處理接口: Mapper
完全需要用戶自己去實現其中:map() setup() clean()
3)map輸出的結果在shuffle階段會被partition以及sort,此處有兩個接口可自定義:
(1)Partitioner
有默認實現 HashPartitioner,邏輯是 根據key和numReduces來返回一個分區號;
key.hashCode()&Integer.MAXVALUE % numReduces
通常情況下,用默認的這個HashPartitioner就可以,如果業務上有特別的需求,可以自定義
(2)Comparable
當我們用自定義的對象作爲key來輸出時,就必須要實現WritableComparable接口,override其中的compareTo()方法
4)reduce端的數據分組比較接口:Groupingcomparator
reduceTask拿到輸入數據(一個partition的所有數據)後,首先需要對數據進行分組,其分組的默認原則是key相同,
然後對每一組kv數據調用一次reduce()方法,並且將這一組kv中的第一個kv的key作爲參數傳給reduce的key,
將這一組數據的value的迭代器傳給reduce()的values參數
利用上述這個機制,我們可以實現一個高效的分組取最大值的邏輯:
自定義一個bean對象用來封裝我們的數據,然後改寫其compareTo方法產生倒序排序的效果
然後自定義一個Groupingcomparator,將bean對象的分組邏輯改成按照我們的業務分組id來分組(比如訂單號)
這樣,我們要取的最大值就是reduce()方法中傳進來key
5)邏輯處理接口:Reducer
完全需要用戶自己去實現其中 reduce() setup() clean()
6)輸出數據接口:OutputFormat---> 有一系列子類FileOutputformat DBoutputFormat .....
默認實現類是TextOutputFormat,功能邏輯是:將每一個KV對向目標文本文件中輸出爲一行