詳解MapReduce中的五大程式設計模型

spacedong發表於2019-03-18

前言

我們上一節講了關於 MapReduce 中的應用場景和架構分析,最後還使用了一個CountWordDemo來進行演示,關於MapReduce的具體操作。如果還不瞭解的朋友可以看看上篇文章:初識MapReduce的應用場景(附JAVA和Python程式碼)

接下來,我們會講解關於MapReduce的程式設計模型,這篇文章的主要目的就是講清楚Mapreduce的程式設計模型有多少種,它們之間是怎麼協調合作的,會盡量從原始碼的角度來解析,最後就是講解不同的語言是如何呼叫Hadoop中的MapreduceAPI的。

目錄

  • MapReduce 程式設計模型的框架
  • 五種程式設計模型的詳解
    • InputFormat
    • OutPutFormat
    • Mapper
    • Reducer
    • Partitioner
  • 總結

MapReduce 程式設計模型的框架

我們先來看一張圖,關於MapReduce的程式設計模型

MapReduce`的框架圖

  • 使用者程式層

使用者程式層是指使用者用編寫好的程式碼來呼叫MapReduce的介面層。

  • 工具層

    • Job control 是為了監控Hadoop中的MapReduce向叢集提交複雜的作業任務,提交了任務到叢集中後,形成的任務是一個有向圖。每一個任務都有兩個方法 submit()waitForCompletion(boolean)submit()方法是向叢集中提交作業,然後立即返回,waitForCompletion(boolean)就是等待叢集中的作業是否已經完成了,如果完成了,得到的結果可以當作下個任務的輸入。
    • chain Mapperchain Reducer 的這個模組,是為了使用者編寫鏈式作業,形式類似於 Map + Reduce Map *,表達的意思就是隻有一個Reduce,在Reduce的前後可以有多個Map
    • Hadoop Streaming支援的是指令碼語言,例Python、PHP等來呼叫Hadoop的底層介面,Hadoop Pipes 支援的是 C++來呼叫。
  • 程式設計介面層,這一層是全部由Java語言來實現的,如果是Java來開發的話,那麼可以直接使用這一層。

詳解五種程式設計模型

InputFormat

作用

對輸入進入MapReduce的檔案進行規範處理,主要包括InputSplitRecordReader兩個部分。TextOutputFormat是預設的檔案輸入格式。

InputForMat中的流程圖

InputSplit

這個是指對輸入的檔案進行邏輯切割,切割成一對對Key-Value值。有兩個引數可以定義InputSplit的塊大小,分別是mapred.max.split.size(記為minSize)和mapred.min.split.size(記為maxSize)。

RecordReader

是指作業在InputSplit中切割完成後,輸出Key-Value對,再由RecordReader進行讀取到一個個Mapper檔案中。如果沒有特殊定義,一個Mapper檔案的大小就是由Hadoopblock_size決定的,Hadoop 1.x中的block_size64M,在Hadoop 2.x中的 block_size的大小就是128M

切割塊的大小

Hadoop2.x以上的版本中,一個splitSize的計算公式為

splitSize = max\{minSize,min\{maxSize, blockSize\}\}
複製程式碼

OutputFormat

作用

對輸出的檔案進行規範處理,主要的工作有兩個部分,一個是檢查輸出的目錄是否已經存在,如果存在的話就會報錯,另一個是輸出最終結果的檔案到檔案系統中,TextOutputFormat是預設的輸出格式。

OnputForMat中的流程圖

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實際上是分三個階段,分別是ShuffleSortSecondary Sort

shuffle

這個階段是指Reducer的輸入階段,系統會為每一個Reduce任務去獲取所有的分塊,通過的是HTTP的方式

sort

這個階段是指在輸入Reducer階段的值進行分組,sortshuffle是同時進行的,可以這麼理解,一邊在輸入的時候,同時在一邊排序。

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的框架模型,分別是分為使用者程式層、工具層、程式設計介面層這三層,在程式設計介面層主要有五種程式設計模型,分別是InputFomatMapperReducePartitionerOnputFomatReducer。主要是偏理論,程式碼的參考例子可以參考官方的例子:WordCount_v2.0

這是MapReduce系列的第二篇,接下來的一篇會詳細寫關於MapReduce的作業配置和環境,結合一些面試題的彙總,所以接下來的這篇還是乾貨滿滿的,期待著就好了。

更多幹貨,歡迎關注我的公眾號:spacedong

image

相關文章