package com.felix;import java.io.IOException;import java.util.Iterator;import java.util.StringTokenizer;import org.apache.hadoop.fs.Path;import org.apache.hadoop.io.IntWritable;import org.apache.hadoop.io.LongWritable;import org.apache.hadoop.io.Text;import org.apache.hadoop.mapred.FileInputFormat;import org.apache.hadoop.mapred.FileOutputFormat;import org.apache.hadoop.mapred.JobClient;import org.apache.hadoop.mapred.JobConf;import org.apache.hadoop.mapred.MapReduceBase;import org.apache.hadoop.mapred.Mapper;import org.apache.hadoop.mapred.OutputCollector;import org.apache.hadoop.mapred.Reducer;import org.apache.hadoop.mapred.Reporter;import org.apache.hadoop.mapred.TextInputFormat;import org.apache.hadoop.mapred.TextOutputFormat;/** * * 描述:WordCount explains by Felix * @author Hadoop Dev Group */public class WordCount{ /** * MapReduceBase類:實現了Mapper和Reducer介面的基類(其中的方法只是實現介面,而未作任何事情) * Mapper介面: * WritableComparable介面:實現WritableComparable的類可以相互比較。所有被用作key的類應該實現此介面。 * Reporter 則可用於報告整個應用的運行進度,本例中未使用。 * */ public static class Map extends MapReduceBase implements Mapper<LongWritable, Text, Text, IntWritable> { /** * LongWritable, IntWritable, Text 均是 Hadoop 中實現的用於封裝 Java 資料類型的類,這些類實現了WritableComparable介面, * 都能夠被序列化從而便於在分布式環境中進行資料交換,你可以將它們分別視為long,int,String 的替代品。 */ private final static IntWritable one = new IntWritable(1); private Text word = new Text(); /** * Mapper介面中的map方法: * void map(K1 key, V1 value, OutputCollector<K2,V2> output, Reporter reporter) * 映射一個單個的輸入k/v對到一個中間的k/v對 * 輸出對不需要和輸入對是相同的類型,輸入對可以映射到0個或多個輸出對。 * OutputCollector介面:收集Mapper和Reducer輸出的<k,v>對。 * OutputCollector介面的collect(k, v)方法:增加一個(k,v)對到output */ public void map(LongWritable key, Text value, OutputCollector<Text, IntWritable> output, Reporter reporter) throws IOException { String line = value.toString(); StringTokenizer tokenizer = new StringTokenizer(line); while (tokenizer.hasMoreTokens()) { word.set(tokenizer.nextToken()); output.collect(word, one); } } } public static class Reduce extends MapReduceBase implements Reducer<Text, IntWritable, Text, IntWritable> { public void reduce(Text key, Iterator<IntWritable> values, OutputCollector<Text, IntWritable> output, Reporter reporter) throws IOException { int sum = 0; while (values.hasNext()) { sum += values.next().get(); } output.collect(key, new IntWritable(sum)); } } public static void main(String[] args) throws Exception { /** * JobConf:map/reduce的job配置類,向hadoop架構描述map-reduce執行的工作 * 構造方法:JobConf()、JobConf(Class exampleClass)、JobConf(Configuration conf)等 */ JobConf conf = new JobConf(WordCount.class); conf.setJobName("wordcount"); //設定一個使用者定義的job名稱 conf.setOutputKeyClass(Text.class); //為job的輸出資料設定Key類 conf.setOutputValueClass(IntWritable.class); //為job輸出設定value類 conf.setMapperClass(Map.class); //為job設定Mapper類 conf.setCombinerClass(Reduce.class); //為job設定Combiner類 conf.setReducerClass(Reduce.class); //為job設定Reduce類 conf.setInputFormat(TextInputFormat.class); //為map-reduce任務設定InputFormat實作類別 conf.setOutputFormat(TextOutputFormat.class); //為map-reduce任務設定OutputFormat實作類別 /** * InputFormat描述map-reduce中對job的輸入定義 * setInputPaths():為map-reduce job設定路徑數組作為輸入列表 * setInputPath():為map-reduce job設定路徑數組作為輸出資料行表 */ FileInputFormat.setInputPaths(conf, new Path(args[0])); FileOutputFormat.setOutputPath(conf, new Path(args[1])); JobClient.runJob(conf); //運行一個job }}
(1)Map-Reduce的處理過程主要涉及以下四個部分: 用戶端Client:用於提交Map-reduce任務job JobTracker:協調整個job的運行,其為一個Java進程,其main class為JobTracker TaskTracker:運行此job的task,處理input split,其為一個Java進程,其main class為TaskTracker HDFS:hadoopDistributed File System,用於在各個進程間共用Job相關的檔案
(2)負責控制及調度MapReduce的job是JobTracker,負責運行MapReduce的job是TaskTracker。
(3)當client執行runJob(),向Hadoop提出請求處理一個job,Hadoop將job分成兩種task進行處理。即:map task和reduce task。
(4)Hadoop將輸入資料分成固定大小塊(這個快不能大於HDFS中每個block的大小64M)存放於DataNode上,這個過程我們稱之為input split。注意:輸入時Text的話則是按照行來split。Hadoop為每一個input split建立一個task,在此task中依次處理split中的一個個記錄(record)。
(5)Hadoop盡量讓輸入資料區塊所在的DataNode和task所執行的DataNode(每個DataNode上都有一個taskTracker)為同一個,可以提高運行效率,所以input split的大小也是HDFS中block的大小。
(6)Map task的輸出一般都是Reduce task的輸入,Reduce的輸出是整個job的輸出,往往會儲存在HDFS中。
(7)Reduce task中相同的key的所有記錄一定會到同一個task Tracker中運行。然而,不同的key可以在不同的task Tracker上運行,我們稱之為partition。
Partition規則:(K2,V2)->Integer,也即,根據k2,產生一個partition的id,具有相同id的k2則進入同一個partition,被同一個TaskTracker上被同一個Reducer進行處理。
(8)任務提交
JobClient.runJob()建立一個新的JobClient執行個體,調用其submitJob()函數。 向JobTracker請求一個新的job ID 檢測此job的output配置 計算此job的input splits 將Job運行所需的資源拷貝到JobTracker的檔案系統中的檔案夾中,包括job jar檔案,job.xml設定檔,input splits 通知JobTracker此Job已經可以運行了
提交任務後,runJob每隔一秒鐘輪詢一次job的進度,將進度返回到命令列,直到任務運行完畢。
(9)任務初始化
當JobTracker收到submitJob調用的時候,將此任務放到一個隊列中,job調度器將從隊列中擷取任務並初始化任務。
初始化首先建立一個對象來封裝job啟動並執行tasks, status以及progress。
在建立task之前,job調度器首先從共用檔案系統中獲得JobClient計算出的input splits。
其為每個input split建立一個map task。
每個task被分配一個ID。
(10)任務分配
TaskTracker周期性的向JobTracker發送heartbeat。
在heartbeat中,TaskTracker告知JobTracker其已經準備運行一個新的task,JobTracker將分配給其一個task。
在JobTracker為TaskTracker選擇一個task之前,JobTracker必須首先按照優先順序選擇一個Job,在最高優先順序的Job中選擇一個task。
TaskTracker有固定數量的位置來運行map task或者reduce task。
預設的調度器對待map task優先於reduce task
當選擇reduce task的時候,JobTracker並不在多個task之間進行選擇,而是直接取下一個,因為reduce task沒有資料本地化的概念。
(11)任務執行
TaskTracker被分配了一個task,下面便要運行此task。
首先,TaskTracker將此job的jar從共用檔案系統中拷貝到TaskTracker的檔案系統中。
TaskTracker從distributed cache中將job運行所需要的檔案拷貝到本地磁碟。
其次,其為每個task建立一個本地的工作目錄,將jar解壓縮到檔案目錄中。
其三,其建立一個TaskRunner來運行task。
TaskRunner建立一個新的JVM來運行task。
被建立的child JVM和TaskTracker通訊來報告運行進度。 3.4.1、Map的過程
MapRunnable從input split中讀取一個個的record,然後依次調用Mapper的map函數,將結果輸出。
map的輸出並不是直接寫入硬碟,而是將其寫入緩衝memory buffer。
當buffer中資料的到達一定的大小,一個背景線程將資料開始寫入硬碟。
在寫入硬碟之前,記憶體中的資料通過partitioner分成多個partition。
在同一個partition中,背景線程會將資料按照key在記憶體中排序。
每次從記憶體向硬碟flush資料,都產生一個新的spill檔案。
當此task結束之前,所有的spill檔案被合并為一個整的被partition的而且排好序的檔案。
reducer可以通過http協議請求map的輸出檔案,tracker.http.threads可以設定http服務線程數。 3.4.2、Reduce的過程
當map task結束後,其通知TaskTracker,TaskTracker通知JobTracker。
對於一個job,JobTracker知道TaskTracer和map輸出的對應關係。
reducer中一個線程周期性的向JobTracker請求map輸出的位置,直到其取得了所有的map輸出。
reduce task需要其對應的partition的所有的map輸出。
reduce task中的copy過程即當每個map task結束的時候就開始拷貝輸出,因為不同的map task完成時間不同。
reduce task中有多個copy線程,可以並行拷貝map輸出。
當很多map輸出拷貝到reduce task後,一個背景線程將其合并為一個大的排好序的檔案。
當所有的map輸出都拷貝到reduce task後,進入sort過程,將所有的map輸出合并為大的排好序的檔案。
最後進入reduce過程,調用reducer的reduce函數,處理排好序的輸出的每個key,最後的結果寫入HDFS。
3.5、任務結束
當JobTracker獲得最後一個task的運行成功的報告後,將job得狀態改為成功。
當JobClient從JobTracker輪詢的時候,發現此job已經成功結束,則向使用者列印訊息,從runJob函數中返回。