小白也能學會的MapReduce編程

小白也能學會的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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章