小白也能學會的MapReduce編程
文章目錄
再議MapReduce
我們知道hadoop的核心有四大組件:
- HDFS
- MapReduce
- YARN
- Common
HDFS:分佈式存儲系統
MapReduce:分佈式計算系統
YARN: hadoop 的資源調度系統
Common: 以上三大組件的底層支撐組件,主要提供基礎工具包和 RPC (遠程過程調用,調用服務器的服務) 框架等
而MapReduce作爲其中的計算系統在大規模數據處理時,有三個層面上的基本構思:
如何對付大數據處理:分而治之
對相互間不具有計算依賴關係的大數據,實現並行最自然的辦法就是採取分而治之的策略
上升到抽象模型:Mapper與Reducer
MPI等並行計算方法缺少高層並行編程模型,爲了克服這一缺陷,MapReduce借鑑了Lisp函數式語言中的思想,用Map和Reduce兩個函數提供了高層的並行編程抽象模型
上升到構架:統一構架,爲程序員隱藏系統層細節
MPI等並行計算方法缺少統一的計算框架支持,程序員需要考慮數據存儲、劃分、分發、結果收集、錯誤恢復等諸多細節;爲此,MapReduce設計並提供了統一的計算框架,爲程序員隱藏了絕大多數系統層面的處理細節
抽象描述Map與Reduce
MapReduce借鑑了函數式程序設計語言Lisp中的思想,定義瞭如下的Map和Reduce兩個抽象的編程接口,由用戶去編程實現:
map: (k1; v1) -> [(k2; v2)]
輸入:鍵值對(k1; v1)表示的數據
處理:文檔數據記錄(如文本文件中的行,或數據表格中的行)將以“鍵值對”形式傳入map函數;map函數將處理這些鍵值對,並以另一種鍵值對形式輸出處理的一組鍵值對中間結果[(k2; v2)]
輸出:鍵值對[(k2; v2)]表示的一組中間數據
reduce: (k2; [v2]) -> [(k3; v3)]
輸入: 由map輸出的一組鍵值對[(k2; v2)] 將被進行合併處理將同樣主鍵下的不同數值合併到一個列表[v2]中,故reduce的輸入爲(k2; [v2])
處理:對傳入的中間結果列表數據進行某種整理或進一步的處理,併產生最終的某種形式的結果輸出[(k3; v3)] 。
輸出:最終輸出結果[(k3; v3)]
Map和Reduce爲程序員提供了一個清晰的操作接口抽象描述
小結
- 各個map函數對所劃分的數據並行處理,從不同的輸入數據產生不同的中間結果輸出
- 各個reduce也各自並行計算,各自負責處理不同的中間結果數據集合
- 進行reduce處理之前,必須等到所有的map函數做完,因此,在進入reduce前需要有一個同步障(barrier);這個階段也負責對map的中間結果數據進行收集整理(aggregation & shuffle)處理,以便reduce更有效地計算最終結果
- 最終彙總所有reduce的輸出結果即可獲得最終結果
MapReduce編程詳解
一個簡單的WordCount程序
Mapper
import java.io.IOException;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
/**
* LongWritable 偏移量 long,表示該行在文件中的位置,而不是行號
* Text map階段的輸入數據 一行文本信息 字符串類型 String
* Text map階段的數據字符串類型 String
* IntWritable map階段輸出的value類型,對應java中的int型,表示行號
*/
public class WorkCountMap
extends Mapper<LongWritable, Text, Text, IntWritable>{
/**
* key 輸入的 鍵
* value 輸入的 值
* context 上下文對象
*/
@Override
protected void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
String line = value.toString();
String[] words = line.split("/t");//分詞
for(String word : words) {
Text wordText = new Text(word);
IntWritable outValue = new IntWritable();
//寫出
context.write(wordText, outValue);
}
}
}
- 尖括號是JAVA的泛型,在這裏約束了函數的輸入數據類型
- 上面代碼中,注意Mapper類的泛型不是java的基本類型,而是Hadoop的數據類型Text、IntWritable。我們可以簡單的等價爲java的類String、int。
代碼中Mapper類的泛型依次是<k1,v1,k2,v2>
。map方法的第二個形參是行文本內容,是我們關心的。核心代碼是把行文本內容按照空格拆分,把每行數據提取出來,單詞作爲新的鍵,數量作爲新的值,寫入到上下文context
中。在這裏,因爲有多組數據,因此每一組都會輸出一個<wordText, outValue>
鍵值對。
Reducer
reduce階段的輸入 是 mapper階段的輸出
import java.io.IOException;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
/**
* Text 數據類型:字符串類型 String
* IntWritable reduce階段的輸入類型 int
* Text reduce階段的輸出數據類型 String類型
* IntWritable 輸出詞頻個數 Int型
*/
public class WorkCountReduce extends Reducer<Text, IntWritable, Text, IntWritable>{
/**
* key 輸入的 鍵
* value 輸入的 值
* context 上下文對象,用於輸出鍵值對
*/
@Override
protected void reduce(Text key, Iterable<IntWritable> value,
Context context) throws IOException, InterruptedException {
int sum=0;
for (IntWritable number : value) {
sum += number.get();
}
//單詞 個數 hadoop,10
context.write(key, new IntWritable(sum));
}
}
主函數
在 Hadoop 中一次計算任務稱之爲一個 job, main函數主要負責新建一個Job對象併爲之設定相應的Mapper和Reducer類,以及輸入、輸出路徑等
public static void main(String[] args) throws Exception
{ //爲任務設定配置文件
Configuration conf = new Configuration();
//命令行參數
String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();
if (otherArgs.length != 2)
{ System.err.println("Usage: wordcount <in> <out>");
System.exit(2);
}
Job job = new Job(conf, “word count”); //新建一個用戶定義的Job
job.setJarByClass(WordCount.class); //設置執行任務的jar
job.setMapperClass(WorkCountMap.class); //設置Mapper類
job.setCombinerClass(WorkCountReduce.class); //設置Combine類
job.setReducerClass(WorkCountReduce.class); //設置Reducer類
job.setOutputKeyClass(Text.class); //設置job輸出的key
//設置job輸出的value
job.setOutputValueClass(IntWritable.class);
//設置輸入文件的路徑
FileInputFormat.addInputPath(job, new Path(otherArgs[0]));
//設置輸出文件的路徑
FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]));
//提交任務並等待任務完成
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
收尾MapReduce核心運行機制
總說
一個完整的 mapreduce 程序在分佈式運行時有兩類實例進程:
(1) MRAppMaster:負責整個程序的過程調度及狀態協調 (該進程在yarn節點上)
(2) Yarnchild:負責 map 階段的整個數據處理流程
(3) Yarnchild:負責 reduce 階段的整個數據處理流程
以上兩個階段 maptask 和 reducetask 的進程都是 yarnchild,並不是說這 maptask 和 reducetask 就跑在同一個 yarnchild 進行裏(Yarnchild進程在運行該命令的節點上)
MapReduce程序的運行流程
(1) 一個 MapReduce 程序啓動的時候,最先啓動的是 MRAppMaster, MRAppMaster 啓動後根據本次 job 的描述信息,計算出需要的 maptask 實例數量,然後向集羣申請機器啓動相應數量的 maptask 進程
(2) maptask 進程啓動之後,根據給定的數據切片(哪個文件的哪個偏移量範圍)範圍進行數 據處理,主體流程爲:
A、 利用客戶指定的 inputformat 來獲取 RecordReader 讀取數據,形成輸入 KV 對
B、 將輸入 KV 對傳遞給客戶定義的 map()方法,做邏輯運算,並將 map()方法輸出的 KV 對收 集到緩存
C、 將緩存中的 KV 對按照 K 分區排序後不斷溢寫到磁盤文件 (超過緩存內存寫到磁盤臨時文件,最後都寫到該文件,ruduce 獲取該文件後,刪除 )
(3) MRAppMaster 監控到所有 maptask 進程任務完成之後(真實情況是,某些 maptask 進 程處理完成後,就會開始啓動 reducetask 去已完成的 maptask 處 fetch 數據),會根據客戶指 定的參數啓動相應數量的 reducetask 進程,並告知 reducetask 進程要處理的數據範圍(數據
分區)
(4) Reducetask 進程啓動之後,根據 MRAppMaster 告知的待處理數據所在位置,從若干臺 maptask 運行所在機器上獲取到若干個 maptask 輸出結果文件,並在本地進行重新歸併排序, 然後按照相同 key 的 KV 爲一個組,調用客戶定義的 reduce()方法進行邏輯運算,並收集運算輸出的結果 KV,然後調用客戶指定的 outputformat 將結果數據輸出到外部存儲
maptask並行度決定機制
maptask 的並行度決定 map 階段的任務處理併發度,進而影響到整個 job 的處理速度。
一個 job 的 map 階段並行度由客戶端在提交 job 時決定, 客戶端對 map 階段並行度的規劃
的基本邏輯爲:
將待處理數據執行邏輯切片(即按照一個特定切片大小,將待處理數據劃分成邏輯上的多 個 split),然後每一個 split 分配一個 mapTask 並行實例處理
這段邏輯及形成的切片規劃描述文件,是由 FileInputFormat
實現類的 getSplits()方法完成的。
該方法返回的是 List<InputSplit>
, InputSplit 封裝了每一個邏輯切片的信息,包括長度和位置 信息,而 getSplits()方法返回一組 InputSplit