Hadoop組件之MapReduce框架

Hadoop組件之MapReduce框架

此處的介紹不是書上的抄寫,只作理解爲主,所以請諒解語言用詞的精確性。而且不可能用一篇文章就學會MapReduce 框架,僅作爲引導。

認識MapReduce

MapReduce是一種編程模型,用於大規模數據集的離線式並行運算。關於實時計算用storm和spark,後面會介紹。"Map(映射)"和"Reduce(歸約)",是它們的主要思想,都是從函數式編程語言裏借來的,還有從矢量編程語言裏借來的特性。"Map(映射)"和"Reduce(歸約)”會結合後面的例子介紹。

MapReduce提供的主要功能

1)數據劃分和計算任務調度:

系統自動將一個作業(Job)待處理的大數據劃分爲很多個數據塊,每個數據塊對應於一個計算任務(Task),並自動 調度計算節點來處理相應的數據塊。作業和任務調度功能主要負責分配和調度計算節點(Map節點或Reduce節點),同時負責監控這些節點的執行狀態,並 負責Map節點執行的同步控制。

2)數據/代碼互定位:

爲了減少數據通信,一個基本原則是本地化數據處理,即一個計算節點儘可能處理其本地磁盤上所分佈存儲的數據,這實現了代碼向 數據的遷移;當無法進行這種本地化數據處理時,再尋找其他可用節點並將數據從網絡上傳送給該節點(數據向代碼遷移),但將儘可能從數據所在的本地機架上尋 找可用節點以減少通信延遲。

3)系統優化:

爲了減少數據通信開銷,中間結果數據進入Reduce節點前會進行一定的合併處理;一個Reduce節點所處理的數據可能會來自多個 Map節點,爲了避免Reduce計算階段發生數據相關性,Map節點輸出的中間結果需使用一定的策略進行適當的劃分處理,保證相關性數據發送到同一個 Reduce節點;此外,系統還進行一些計算性能優化處理,如對最慢的計算任務採用多備份執行、選最快完成者作爲結果。

4)出錯檢測和恢復:

以低端商用服務器構成的大規模MapReduce計算集羣中,節點硬件(主機、磁盤、內存等)出錯和軟件出錯是常態,因此 MapReduce需要能檢測並隔離出錯節點,並調度分配新的節點接管出錯節點的計算任務。同時,系統還將維護數據存儲的可靠性,用多備份冗餘存儲機制提 高數據存儲的可靠性,並能及時檢測和恢復出錯的數據。

MapReduce設計上特徵

1)向“外”橫向擴展,而非向“上”縱向擴展

對於大規模數據處理,由於有大量數據存儲需要,顯而易見,基於低端服務器的集羣遠比基於高端服務器的集羣優越,這就是爲什麼MapReduce並行計算集羣會基於低端服務器實現的原因。

2)失效被認爲是常態

MapReduce集羣中使用大量的低端服務器,因此,節點硬件失效和軟件出錯是常態,MapReduce並行計算軟件框架使用了多種有效的錯誤檢測和恢復機制,如節點自動重啓技術,使集羣和計算框架具有對付節點失效的健壯性,能有效處理失效節點的檢測和恢復。

3)處理偏向數據

傳統高性能計算系統通常有很多處理器節點與一些外存儲器節點相連,如用存儲區域網絡(Storage Area,SAN Network)連接的磁盤陣列,大規模數據處理時外存文件數據I/O訪問會成爲一個制約系統性能的瓶頸。MapReduce採用了數據/代碼互定位的技術方法,計算節點將首先儘量負責計算其本地存儲的數據,以發揮數據本地化特點,僅當節點無法處理本地數據時,再採用就近原則尋找其他可用計算節點,並把數據傳送到該可用計算節點。

4)順序處理數據、避免隨機訪問數據

爲了實現面向大數據集批處理的高吞吐量的並行處理,MapReduce可以利用集羣中 的大量數據存儲節點同時訪問數據,以此利用分佈集羣中大量節點上的磁盤集合提供高帶寬的數據訪問和傳輸。

5)爲應用開發者隱藏系統層細節

MapReduce提供了一種抽象機制將程序員與系統層細節隔離開來,程序員僅需描述需要計算什麼(What to compute),而具體怎麼去計算(How to compute)就交由系統的執行框架處理,這樣程序員可從系統層細節中解放出來,而致力於其應用本身計算問題的算法設計。

6)平滑無縫的可擴展性

這裏指出的可擴展性主要包括兩層意義上的擴展性:數據擴展和系統規模擴展性。

理想的軟件算法應當能隨着數據規模的擴大而表現出持續的有效性,性能上的下降程度應與數據規模擴大的倍數相當;在集羣規模上,要求算法的計算性能應能隨着節點數的增加保持接近線性程度的增長。

MapReduce組件

MapReduce的必備組件:

•驅動器(Driver) : 負責初始化一個MapReduce作業並定義特定作業的運行環境配置,通過 driver還可以獲取作業執行的狀態;

•上下文(Context) : 在MapReduce作業執行的任何階段都可用,提供了一種方便的機制可以用來交換作業運行所需系統和作業範圍信息;

•輸入數據: MapReduce 作業的輸入數據最初被保存的地方;

•輸入格式: 定義了輸入數據如何被讀取和切分;
•輸入切分: 定義了MapReduce程序中單個map任務的工作單元;

•輸出格式: 管控着作業輸出的寫方式;

•RecordReader: RecordReader類實際是從其數據源讀取數據並將該數據轉換成適合被mapper處理的鍵值對,然後將這些鍵值對傳遞給map方法;

•Mapper: mapper執行MapReduce程序第一階段的用戶自定義動作;
•分區: Partitioner 類決定了一個給定的鍵值對將被髮到哪個reducer,默認的Partitioner 爲每個鍵計算哈希值並基於該結果分配分區;
•Shuffle: 將map輸出移動到對應的reducers的過程稱爲shuffling;
•RecordWriter: RecordWriter 定義了單個輸出如何被寫入;
•排序: 對於指定reducer,任務計算的中間結果鍵值對集合在送往該reducer之前,Hadoop就會自動對其排序。

以下爲MapReduce的可選組件:

•組合器: 組合器通常在reducer之前,mapper之後運行。一個Combiner類的實例通常在每個map任務和一些reduce任務中運行,組合器接收mapper實例分發的數據作爲輸入,並嘗試將相同的鍵對應的值組合,從而縮小了鍵空間也減小了需要存儲的鍵的數量,組合器的輸出先被 排序然後分發到reducers;

• 分佈式緩存: 這個設計允許在集羣中的所有節點上進行數據共享,分佈式緩存應該是可以被每個任務訪問的共享庫,一個保存着鍵值對的全局查詢文件,包含着可執行代碼的jar文件等,緩存會將文件拷貝至實際執行的機器上,並保證本地可用。

此圖爲MapReduce框架中的各組件及各組件間的關係。

<img src="Screen%20Shot%202016-05-24%20at%207.05.37%20PM.png"/>

MapReduce的作業提交

兩種 APIs使用方式,通過使用Job對象來提交一個作業 :

•使用 submit 方法提交一個運行作業並立即返回;
•waitForCompletion方法提交一個作業,並監控它的執行過程,並且只在作業完成後返回(常用的job提交方法)。

作業設置由以下幾個部分組成 :

•輸入設置: 即設置InputFormat,它負責計算任務的分片和數據讀取器的創建;
•Mapper設置: 設置作業要使用的mapper 類;

•Reducer設置: 設置作業要使用的reducer 類。此外,用戶還可以設置作業所使用的reducers的數量;
•輸出設置: 設置輸出格式, 負責執行結果的輸出,這個類的主要功能是創建一個OutputWriter。

MapReduce的Hello World-WordCount

每種語言都有自己的一個Hello World程序作爲入門程序,MapReduce是基於java語言,但它有自己的Hello World-WordCount,希望藉此入門程序能說清楚MapReduce。新版本api和舊版本api有區別是什麼呢?在《Hadoop技術內幕 深入理解MapReduce架構設計與實現原理》裏有詳細的講解。我的代碼使用新的API。

MapReduce的流程圖如下:

<img src="Screen%20Shot%202016-06-04%20at%2012.09.41%20PM.png"/>

關於怎麼建項目等操作過程網上很多,在此處就不給出了。改實例用到的數據就是一篇英文文章即可,直接上代碼吧!我把我的WordCount分爲3個class文件。

Mapper class

package com.combiner.source;

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;

public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable>{

@Override

public void map(LongWritable inputKey,Text inputVal,Context context)

throws IOException, InterruptedException {

String line = inputVal.toString();

String[] splits = line.trim().split("\\W+");

for(String outputKey:splits) {

context.write(new Text(outputKey), new IntWritable(1));

}

}

}

下面我對代碼做簡單的描述,WordCountMapper要實現Mapper方法就需要繼承Mapper,而且同時要指定Mapper類的泛型:

public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable>{……}

此處泛型的作用是指定 WordCountMapper 方法的輸入鍵值對的類型和輸出鍵值對的類型,格式爲<輸入鍵值對中鍵的類型, 輸入鍵值對中值的類型, 輸出鍵值對中鍵的類型, 輸出鍵值對中值的類型>。

自己的邏輯部分:

String line = inputVal.toString();

String[] splits = line.trim().split("\\W+");

獲取每行的文本並根據規則切割後存入String類型的數組中。

注:每個人劃分的規則不完全相同。

在實現自己的邏輯後,使用context對象的write方法進行輸出:

for(String outputKey:splits)

context.write(new Text(outputKey), new IntWritable(1));

}

context對象會保存作業運行的上下文信息,如作業的配置信息、InputSplit信息、任務ID等。

Reducer class

package com.combiner.source;

import java.io.IOException;

import org.apache.hadoop.io.IntWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapreduce.Reducer;

public class WordCountReducer extends Reducer<Text, IntWritable, Text, IntWritable> {

@Override

public void reduce(Text key,Iterable<IntWritable> listOfValues,Context context)

throws IOException,InterruptedException {

int sum=0;

for(IntWritable val:listOfValues) {

sum = sum + val.get();

}

context.write(key,new IntWritable(sum));

}

}

WordCountReducer在繼承Reducer的同時同樣要指定Reducer類的泛型:

public class WordCountReducer extends Reducer<Text, IntWritable, Text, IntWritable> {......}

此處泛型的作用是指定 reduce 方法的輸入鍵值對(中間結果)的類型和輸出鍵值對(最終結果)的類型,格式同Mapper。

實現自己的邏輯,轉化爲代碼:一個計數器的功能。

int sum=0;

for(IntWritable val:listOfValues) {

sum = sum + val.get();

}

之後同樣通過context的write方法進行輸出結果,注意輸出的類型要和泛型一致,否則會報錯。如:context.write(key, value);

與map函數不同的是,reduce函數接受的values參數類型爲 Iterable ,該對象經過聚合後的中間結果需要通過迭代的方式對其進行處理,如:

Iterator<Text> value = values.iterator();

Driver class

package com.combiner.source;

import org.apache.hadoop.fs.Path;

import org.apache.hadoop.io.IntWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapreduce.Job;

import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;

import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;

import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

public class WordCountDriver {

public static void main(String[] args) throws Exception {

Configuration conf = new Configuration();

Job job = new Job(conf,"Basic Word Count Job");

job.setJarByClass(WordCountDriver.class);

job.setMapperClass(WordCountMapper.class);

job.setCombinerClass(WordCountMapper.class);

job.setReducerClass(WordCountReducer.class);

job.setNumReduceTasks( 1 );

job.setInputFormatClass(TextInputFormat.class);

job.setOutputFormatClass(TextOutputFormat.class);

job.setMapOutputKeyClass(Text.class);

job.setMapOutputValueClass(IntWritable.class);

job.setOutputKeyClass(Text.class);

job.setOutputValueClass(IntWritable.class);

FileInputFormat.addInputPath(job, new Path(args[0]));

FileOutputFormat.setOutputPath(job, new Path(args[1]));

System.exit(job.waitForCompletion(true) ? 0 : 1);

}

}

開始main函數調用了:

Configuration conf = new Configuration();

運行mapreduce程序前都要初始化Configuration,該類主要是讀取mapreduce系統配置信息,這些信息包括hdfs還有mapreduce,也就是安裝hadoop時候的配置文件例如:core-site.xml、hdfs-site.xml和mapred-site.xml等等文件裏的信息,有些童鞋不理解爲啥要這麼做,這個是沒有深入思考mapreduce計算框架造成,我們程序員開發mapreduce時候只是在填空,在map函數和reduce函數裏編寫實際進行的業務邏輯,其它的工作都是交給mapreduce框架自己操作的,但是至少我們要告訴它怎麼操作啊,比如hdfs在哪裏啊,mapreduce的jobstracker在哪裏啊,而這些信息就在conf包下的配置文件裏。

String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();

    if (otherArgs.length != 2) {

      System.err.println("Usage: wordcount <in> <out>");

      System.exit(2);

    }

在我的程序中並沒用該語句,是因爲這個並不會影響我程序的運行,If的語句好理解,就是運行WordCount程序時候一定是兩個參數,如果不是就會報錯退出。至於第一句裏的GenericOptionsParser類,它是用來解釋常用hadoop命令,並根據需要爲Configuration對象設置相應的值,其實平時開發裏我們不太常用它,而是讓類實現Tool接口,然後再main函數裏使用ToolRunner運行程序,而ToolRunner內部會調用GenericOptionsParser。一般都不會寫。

Job job = new Job(conf,"Basic Word Count Job");

job.setJarByClass(WordCountDriver.class);

job.setMapperClass(WordCountMapper.class);

job.setCombinerClass(WordCountMapper.class);

job.setReducerClass(WordCountReducer.class);

job.setNumReduceTasks( 1 );

第一行就是在構建一個job,在mapreduce框架裏一個mapreduce任務也叫mapreduce作業也叫做一個mapreduce的job,而具體的map和reduce運算就是task了,這裏我們構建一個job,構建時候有兩個參數,一個是conf,一個是這個job的名稱。第二行就是裝載程序員編寫好的計算程序,例如我們的程序類名就是WordCountDriver了。這裏要做下糾正,雖然此處編寫mapreduce程序只需要實現map函數和reduce函數,但是實際開發我們要實現三個類,第三個類是爲了配置mapreduce如何運行map和reduce函數,準確的說就是構建一個mapreduce能執行的job了,例如WordCount類。給出另一種 Driver class:

package com.combiner.source;

import org.apache.hadoop.conf.Configured;

import org.apache.hadoop.fs.Path;

import org.apache.hadoop.io.IntWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapreduce.Job;

import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;

import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;

import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

import org.apache.hadoop.util.Tool;

import org.apache.hadoop.util.ToolRunner;

public class WordCountDriver extends Configured implements Tool{

public static void main(String[] args) throws Exception {

ToolRunner.run(new WordCountDriver(),args);

}

@Override

public int run(String[] args) throws Exception {

Job job = new Job(getConf(),"Basic Word Count Job");

job.setJarByClass(WordCountDriver.class);

//Map and Reduce

job.setMapperClass(WordCountMapper.class);

job.setReducerClass(WordCountReducer.class);

job.setNumReduceTasks( 1 );

job.setInputFormatClass(TextInputFormat.class);

//the map output

job.setMapOutputKeyClass(Text.class);

job.setMapOutputValueClass(IntWritable.class);

//the reduce output

job.setOutputKeyClass(Text.class);

job.setOutputValueClass(IntWritable.class);

FileInputFormat.addInputPath(job, new Path(args[0]));

FileOutputFormat.setOutputPath(job, new Path(args[1]));

//System.exit(job.waitForCompletion(true) ? 0 : job.waitForCompletion(true);

return 0;

}

}

第三行和第五行就是裝載map函數和reduce函數實現類了,這裏多了個第四行,這個是裝載Combiner類,這個我後面講mapreduce運行機制時候會講述,其實本例去掉第四行也沒有關係,但是使用了第四行理論上運行效率會更好。最後一行的作用是設置ReduceTasks的數量,可以不用設置,當你不設置時,MapReduce會根據的map自動給出ReduceTasks的數量。

job.setInputFormatClass(TextInputFormat.class);

job.setOutputFormatClass(TextOutputFormat.class);

設置job的輸入輸出格式。這條語句保證了輸入文件會按照我們預設的格式被讀取,TextInputFormat和TextOutputFormat即爲我們設定的數據讀取格式。

job.setMapOutputKeyClass(Text.class);

job.setMapOutputValueClass(IntWritable.class);

job.setOutputKeyClass(Text.class);

job.setOutputValueClass(IntWritable.class);

這個是定義map和Reduce輸出的key/value的類型,其中reduce的輸出的key/value的類型也就是最終存儲在hdfs上結果文件的key/value的類型。

FileInputFormat.addInputPath(job, new Path(args[0]));

FileOutputFormat.setOutputPath(job, new Path(args[1]));

System.exit(job.waitForCompletion(true) ? 0 : 1);

第一行就是構建輸入的數據文件路徑,第二行是構建輸出的數據文件路徑,最後一行如果job運行成功了,我們的程序就會正常退出。FileInputFormat和FileOutputFormat是很有學問的,我會在下面的mapreduce運行機制會介紹到它們。mapreduce裏的hello word程序介紹完畢。

注意:reduce 函數的輸入類型必須與 map 函數的輸出類型相同,但 reduce 函數的輸出類型可以不同於輸入。

How MapReduce Works-MapReduce運行機制

因爲Hadoop1.xx與Hadoop2.xx之間有較大的差別,暫時以Hadoop1.xx爲例,至於Hadoop2.xx將在以後補上。經典的MapReduce(MapReduce 1),下圖解釋了作業在經典的MapReduce中運行的工作原理。

<img src="Screen%20Shot%202016-06-04%20at%202.33.24%20PM.png"/>

在最頂層包含4個獨立的實體:

客戶端,提交job

jobtracker,協調作業的運行,它是一個java應用程序

tasktracker,運行作業被劃分後的任務,它同樣是一個java應用程序

分佈式文件系統(一般爲HDFS),用來在實體之間共享作業文件

在MapReduce的運行機制中涉及:

1、作業的提交

2、作業的初始化

3、任務的分配

4、執行任務

5、進度和狀態的更新

6、作業的完成

MapReduce運行流程角度理解

1、是客戶端要編寫好mapreduce程序,配置好mapreduce的作業也就是job,接下來就是提交job了,提交job是提交到JobTracker上的,這個時候JobTracker就會構建這個job,具體就是分配一個新的job任務的ID值,接下來它會做檢查操作,這個檢查就是確定輸出目錄是否存在,如果存在那麼job就不能正常運行下去,JobTracker會拋出錯誤給客戶端,接下來還要檢查輸入目錄是否存在,如果不存在同樣拋出錯誤,如果存在JobTracker會根據輸入計算輸入分片(Input Split),如果分片計算不出來也會拋出錯誤,這些都做好了JobTracker就會配置Job需要的資源了。

2、分配好資源後,JobTracker就會初始化作業,初始化主要做的是將Job放入一個內部的隊列,讓配置好的作業調度器能調度到這個作業,作業調度器會初始化這個job,初始化就是創建一個正在運行的job對象(封裝任務和記錄信息),以便JobTracker跟蹤job的狀態和進程。

3、初始化完畢後,作業調度器會獲取輸入分片信息(input split),每個分片創建一個map任務。接下來就是任務分配了,這個時候tasktracker會運行一個簡單的循環機制定期發送心跳給jobtracker,心跳間隔是5秒,程序員可以配置這個時間,心跳就是jobtracker和tasktracker溝通的橋樑,通過心跳,jobtracker可以監控tasktracker是否存活,也可以獲取tasktracker處理的狀態和問題,同時tasktracker也可以通過心跳裏的返回值獲取jobtracker給它的操作指令。

4&5、任務分配好後就是執行任務了。在任務執行時候jobtracker可以通過心跳機制監控tasktracker的狀態和進度,同時也能計算出整個job的狀態和進度,而tasktracker也可以本地監控自己的狀態和進度。

6、當jobtracker獲得了最後一個完成指定任務的tasktracker操作成功的通知時候,jobtracker會把整個job狀態置爲成功,然後當客戶端查詢job運行狀態時候(注意:這個是異步操作),客戶端會查到job完成的通知的。如果job中途失敗,mapreduce也會有相應機制處理,一般而言如果不是程序員程序本身有bug,mapreduce錯誤處理機制都能保證提交的job能正常完成。

邏輯實體的角度理解

mapreduce運行機制,這些按照時間順序包括:輸入分片(input split)、map階段、combiner階段、shuffle階段和reduce階段。

1、 輸入分片(input split):在進行map計算之前,mapreduce會根據輸入文件計算輸入分片(input split),每個輸入分片(input split)針對一個map任務,輸入分片(input split)存儲的並非數據本身,而是一個分片長度和一個記錄數據的位置的數組,輸入分片(input split)往往和hdfs的block(塊)關係很密切,假如我們設定hdfs的塊的大小是64mb,如果我們輸入有三個文件,大小分別是3mb、65mb和127mb,那麼mapreduce會把3mb文件分爲一個輸入分片(input split),65mb則是兩個輸入分片(input split)而127mb也是兩個輸入分片(input split),換句話說我們如果在map計算前做輸入分片調整,例如合併小文件,那麼就會有5個map任務將執行,而且每個map執行的數據大小不均,這個也是mapreduce優化計算的一個關鍵點。

2、 map階段:就是程序員編寫好的map函數了,因此map函數效率相對好控制,而且一般map操作都是本地化操作也就是在數據存儲節點上進行;

3、combiner階段:combiner階段是程序員可以選擇的,combiner其實也是一種reduce操作,因此我們看見WordCount類裏是用reduce進行加載的。Combiner是一個本地化的reduce操作,它是map運算的後續操作,主要是在map計算出中間文件前做一個簡單的合併重複key值的操作,例如我們對文件裏的單詞頻率做統計,map計算時候如果碰到一個hadoop的單詞就會記錄爲1,但是這篇文章裏hadoop可能會出現n多次,那麼map輸出文件冗餘就會很多,因此在reduce計算前對相同的key做一個合併操作,那麼文件會變小,這樣就提高了寬帶的傳輸效率,畢竟hadoop計算力寬帶資源往往是計算的瓶頸也是最爲寶貴的資源,但是combiner操作是有風險的,使用它的原則是combiner的輸入不會影響到reduce計算的最終輸入,例如:如果計算只是求總數,最大值,最小值可以使用combiner,但是做平均值計算使用combiner的話,最終的reduce計算結果就會出錯。

4、shuffle階段:將map的輸出作爲reduce的輸入的過程就是shuffle了,這個是mapreduce優化的重點地方。這裏我不講怎麼優化shuffle階段,講講shuffle階段的原理,因爲大部分的書籍裏都沒講清楚shuffle階段。Shuffle一開始就是map階段做輸出操作,一般mapreduce計算的都是海量數據,map輸出時候不可能把所有文件都放到內存操作,因此map寫入磁盤的過程十分的複雜,更何況map輸出時候要對結果進行排序,內存開銷是很大的,map在做輸出時候會在內存裏開啓一個環形內存緩衝區,這個緩衝區專門用來輸出的,默認大小是100mb,並且在配置文件裏爲這個緩衝區設定了一個閥值,默認是0.80(這個大小和閥值都是可以在配置文件裏進行配置的),同時map還會爲輸出操作啓動一個守護線程,如果緩衝區的內存達到了閥值的80%時候,這個守護線程就會把內容寫到磁盤上,這個過程叫spill,另外的20%內存可以繼續寫入要寫進磁盤的數據,寫入磁盤和寫入內存操作是互不干擾的,如果緩存區被撐滿了,那麼map就會阻塞寫入內存的操作,讓寫入磁盤操作完成後再繼續執行寫入內存操作,前面我講到寫入磁盤前會有個排序操作,這個是在寫入磁盤操作時候進行,不是在寫入內存時候進行的,如果我們定義了combiner函數,那麼排序前還會執行combiner操作。每次spill操作也就是寫入磁盤操作時候就會寫一個溢出文件,也就是說在做map輸出有幾次spill就會產生多少個溢出文件,等map輸出全部做完後,map會合並這些輸出文件。這個過程裏還會有一個Partitioner操作,對於這個操作很多人都很迷糊,其實Partitioner操作和map階段的輸入分片(Input split)很像,一個Partitioner對應一個reduce作業,如果我們mapreduce操作只有一個reduce操作,那麼Partitioner就只有一個,如果我們有多個reduce操作,那麼Partitioner對應的就會有多個,Partitioner因此就是reduce的輸入分片,這個程序員可以編程控制,主要是根據實際key和value的值,根據實際業務類型或者爲了更好的reduce負載均衡要求進行,這是提高reduce效率的一個關鍵所在。到了reduce階段就是合併map輸出文件了,Partitioner會找到對應的map輸出文件,然後進行復制操作,複製操作時reduce會開啓幾個複製線程,這些線程默認個數是5個,程序員也可以在配置文件更改複製線程的個數,這個複製過程和map寫入磁盤過程類似,也有閥值和內存大小,閥值一樣可以在配置文件裏配置,而內存大小是直接使用reduce的tasktracker的內存大小,複製時候reduce還會進行排序操作和合並文件操作,這些操作完了就會進行reduce計算了。

5、reduce階段:和map函數一樣也是程序員編寫的,最終結果是存儲在hdfs上的。

FileInputFormat類是所有使用文件作爲其數據源的InputFormat實現的基類,它提供兩個功能:一個用於指定作業的的輸入文件的位置;一個是是輸入文件生成分片的實現代碼段,把分片分割成記錄的作業由其子類來完成。

參考:

《Hadoop權威指南》

《Hadoop海量數據處理》

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