Hadoop之MapReduce运行理解

来源:互联网 发布:cms程序 编辑:程序博客网 时间:2024/05/16 01:08
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:hadoop分布式文件系统,用于在各个进程间共享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。

 

image

 

3.5、任务结束

 

当JobTracker获得最后一个task的运行成功的报告后,将job得状态改为成功。

当JobClient从JobTracker轮询的时候,发现此job已经成功结束,则向用户打印消息,从runJob函数中返回。


原创粉丝点击