前言
我們上一節講了關於 MapReduce
中的應用場景和架構分析,最後還使用了一個CountWord
的Demo
來進行演示,關於MapReduce
的具體操作。如果還不瞭解的朋友可以看看上篇文章:初識MapReduce的應用場景(附JAVA和Python程式碼)
接下來,我們會講解關於MapReduce
的程式設計模型,這篇文章的主要目的就是講清楚Mapreduce
的程式設計模型有多少種,它們之間是怎麼協調合作的,會盡量從原始碼的角度來解析,最後就是講解不同的語言是如何呼叫Hadoop
中的Mapreduce
的API
的。
目錄
- MapReduce 程式設計模型的框架
- 五種程式設計模型的詳解
- InputFormat
- OutPutFormat
- Mapper
- Reducer
- Partitioner
- 總結
MapReduce 程式設計模型的框架
我們先來看一張圖,關於MapReduce
的程式設計模型
- 使用者程式層
使用者程式層是指使用者用編寫好的程式碼來呼叫MapReduce
的介面層。
-
工具層
- Job control 是為了監控
Hadoop
中的MapReduce
向叢集提交複雜的作業任務,提交了任務到叢集中後,形成的任務是一個有向圖。每一個任務都有兩個方法submit()
和waitForCompletion(boolean)
,submit()
方法是向叢集中提交作業,然後立即返回,waitForCompletion(boolean)
就是等待叢集中的作業是否已經完成了,如果完成了,得到的結果可以當作下個任務的輸入。 chain Mapper
和chain Reducer
的這個模組,是為了使用者編寫鏈式作業,形式類似於Map + Reduce Map *
,表達的意思就是隻有一個Reduce
,在Reduce
的前後可以有多個Map
Hadoop Streaming
支援的是指令碼語言,例Python、PHP等來呼叫Hadoop
的底層介面,Hadoop Pipes
支援的是C++
來呼叫。
- Job control 是為了監控
-
程式設計介面層,這一層是全部由
Java
語言來實現的,如果是Java
來開發的話,那麼可以直接使用這一層。
詳解五種程式設計模型
InputFormat
作用
對輸入進入MapReduce
的檔案進行規範處理,主要包括InputSplit
和RecordReader
兩個部分。TextOutputFormat
是預設的檔案輸入格式。
InputSplit
這個是指對輸入的檔案進行邏輯切割,切割成一對對Key-Value
值。有兩個引數可以定義InputSplit
的塊大小,分別是mapred.max.split.size
(記為minSize
)和mapred.min.split.size
(記為maxSize
)。
RecordReader
是指作業在InputSplit
中切割完成後,輸出Key-Value
對,再由RecordReader
進行讀取到一個個Mapper
檔案中。如果沒有特殊定義,一個Mapper
檔案的大小就是由Hadoop
的block_size
決定的,Hadoop 1.x
中的block_size
是64M
,在Hadoop 2.x
中的
block_size
的大小就是128M
。
切割塊的大小
在Hadoop2.x
以上的版本中,一個splitSize
的計算公式為
splitSize = max\{minSize,min\{maxSize, blockSize\}\}
複製程式碼
OutputFormat
作用
對輸出的檔案進行規範處理,主要的工作有兩個部分,一個是檢查輸出的目錄是否已經存在,如果存在的話就會報錯,另一個是輸出最終結果的檔案到檔案系統中,TextOutputFormat
是預設的輸出格式。
OutputCommiter
OutputCommiter
的作用有六點:
- 作業(
job
)的初始化
//進行作業的初始化,建立臨時目錄。
//如果初始化成功,那麼作業就會進入到 Running 的狀態
public abstract void setupJob(JobContext var1) throws IOException;
複製程式碼
- 作業執行結束後就刪除作業
//如果這個job完成之後,就會刪除掉這個job。
//例如刪除掉臨時的目錄,然後會宣佈這個job處於以下的三種狀態之一,SUCCEDED/FAILED/KILLED
@Deprecated
public void cleanupJob(JobContext jobContext) throws IOException {
}
複製程式碼
- 初始化
Task
//初始化Task的操作有建立Task的臨時目錄
public abstract void setupTask(TaskAttemptContext var1) throws IOException;
複製程式碼
- 檢查是否提交
Task
的結果
//檢查是否需要提交Task,為的是Task不需要提交的時候提交出去
public abstract boolean needsTaskCommit(TaskAttemptContext var1) throws IOException;
複製程式碼
- 提交
Task
//任務結束的時候,需要提交任務
public abstract void commitTask(TaskAttemptContext var1) throws IOException;
複製程式碼
- 回退
Task
//如果Task處於KILLED或者FAILED的狀態,這Task就會進行刪除掉臨時的目錄
//如果這個目錄刪除不了(例如出現了異常後,處於被鎖定的狀態),另一個同樣的Task會被執行
//然後使用同樣的attempt-id去把這個臨時目錄給刪除掉,也就說,一定會把臨時目錄給刪除乾淨
public abstract void abortTask(TaskAttemptContext var1) throws IOException;
複製程式碼
處理Task Side-Effect File
在Hadoop
中有一種特殊的檔案和特殊的操作,那就是Side-Eddect File
,這個檔案的存在是為了解決某一個Task
因為網路或者是機器效能的原因導致的執行時間過長,從而導致拖慢了整體作業的進度,所以會為每一個任務在另一個節點上再執行一個子任務,然後選擇兩者中處理得到的結果最快的那個任務為最終結果,這個時候為了避免檔案都輸入在同一個檔案中,所以就把備胎任務輸出的檔案取作為 Side-Effect File
RecordWriter
這個是指輸出KEY-VALUE
對到檔案中。
Mapper和Reducer
詳解Mapper
InputFormat
為每一個 InputSplit
生成一個 map
任務,mapper
的實現是通過job
中的setMapperClass(Class)
方法來配置寫好的map
類,如這樣
//設定要執行的mapper類
job.setMapperClass(WordMapper.class);
複製程式碼
其內部是呼叫了map(WritableComparable, Writable, Context)
這個方法來為每一個鍵值對寫入到InputSplit
,程式會呼叫cleanup(Context)
方法來執行清理任務,清理掉不需要使用到的中間值。
關於輸入的鍵值對型別不需要和輸出的鍵值對型別一樣,而且輸入的鍵值對可以對映到0個或者多個鍵值對。通過呼叫context.write(WritableComparable, Writable)
來收集輸出的鍵值對。程式使用Counter
來統計鍵值對的數量,
在Mapper
中的輸出被排序後,就會被劃分到每個Reducer
中,分塊的總數目和一個作業的reduce
任務的數目是一樣的。
需要多少個Mapper任務
關於一個機器節點適合多少個map
任務,官方的文件的建議是,一個節點有10
個到100
個任務是最好的,如果是cpu
低消耗的話,300
個也是可以的,最合理的一個map任務是需要執行超過1
分鐘。
詳解Reducer
Reducer
任務的話就是將Mapper
中輸出的結果進行統計合併後,輸出到檔案系統中。
使用者可以自定義Reducer
的數量,使用Job.setNumReduceTasks(int)
這個方法。
在呼叫Reducer
的話,使用的是Job.setReducerClass(Class)
方法,內部呼叫的是reduce(WritableComparable, Iterable<Writable>, Context)
這個方法,最後,程式會呼叫cleanup(Context)
來進行清理工作。如這樣:
//設定要執行的reduce類
job.setReducerClass(WordReduce.class);
複製程式碼
Reducer
實際上是分三個階段,分別是Shuffle
、Sort
和Secondary Sort
。
shuffle
這個階段是指Reducer
的輸入階段,系統會為每一個Reduce
任務去獲取所有的分塊,通過的是HTTP
的方式
sort
這個階段是指在輸入Reducer
階段的值進行分組,sort
和shuffle
是同時進行的,可以這麼理解,一邊在輸入的時候,同時在一邊排序。
Secondary Sort
這個階段不是必需的,只有在中間過程中對key
的排序和在reduce
的輸入之前對key
的排序規則不同的時候,才會啟動這個過程,可以通過的是Job.setSortComparatorClass(Class)
來指定一個Comparator
進行排序,然後再結合Job.setGroupingComparatorClass(Class)
來進行分組,最後可以實現二次排序。
在整個reduce
中的輸出是沒有排序
需要多少個 Reducer 任務
建議是0.95
或者是1.75
*mapred.tasktracker.reduce.tasks.maximum
。如果是0.95
的話,那麼就可以在mapper
任務結束時,立馬就可以啟動Reducer
任務。如果是1.75
的話,那麼執行的快的節點就可以在map
任務完成的時候先計算一輪,然後等到其他的節點完成的時候就可以計算第二輪了。當然,Reduce
任務的個數不是越多就越好的,個數多會增加系統的開銷,但是可以在提升負載均衡,從而降低由於失敗而帶來的負面影響。
Partitioner
這個模組用來劃分鍵值空間,控制的是map
任務中的key
值分割的分割槽,預設使用的演算法是雜湊函式,HashPartitioner
是預設的Partitioner
。
總結
這篇文章主要就是講了MapReduce
的框架模型,分別是分為使用者程式層、工具層、程式設計介面層這三層,在程式設計介面層主要有五種程式設計模型,分別是InputFomat
、MapperReduce
、Partitioner
、OnputFomat
和Reducer
。主要是偏理論,程式碼的參考例子可以參考官方的例子:WordCount_v2.0
這是MapReduce
系列的第二篇,接下來的一篇會詳細寫關於MapReduce
的作業配置和環境,結合一些面試題的彙總,所以接下來的這篇還是乾貨滿滿的,期待著就好了。
更多幹貨,歡迎關注我的公眾號:spacedong