MapReduce原理

来源:互联网 发布:尧十三 知乎 编辑:程序博客网 时间:2024/06/05 08:18

MapReduce程序的执行过程分为两个阶段:Mapper阶段和Reducer阶段。

其中Mapper阶段可以分为6个步骤:

第一阶段:

先将HDFS中的输入文件file按照一定的标准进行切片,默认切片的类为FileInputFormat。FileInputFormat这个类继承自InputFormat,InputFormat这个类会将文件file按照逻辑进行划分,划分成的每一个split切片将会被分配给一个Mapper任务,通过切片输入文件将会变成split1、split2、split3……等块;随后对输入切片split块按照一定的规则解析成键值对<k1,v1>,默认处理的类为TextInputFormat(TextInputFormat这个类继承自FileInputFormat)。其中k1就是我们常说的起始偏移量,v1就是行文本的内容。

 

注:

文件先被切分为split块,而后每一个split切片对应一个Mapper任务。FileInputFormat这个类先对输入文件进行逻辑上的划分,以64MB为单位,将原始数据从逻辑上分割成若干个split,每个split切片对应一个Mapper任务。

 

对于FileInputFormat这个类,我们需要注意:FileInputFormat这个类只划分比HDFS的block块大的文件,所以FileInputFormat划分的结果是这个文件或者是这个文件中的一部分。如果一个文件的大小比block块小,将不会被FileInputFormat这个类进行逻辑上的划分,此时每一个小文件都会当做一个split块并分配一个Mapper任务,导致效率低下。这也是Hadoop处理大文件的效率要比处理很多小文件的效率高的原因。

 

FileInputFormat这个类将文件file切分成block块之后,TextInputFormat这个类随后将每个split块中的每行记录解析成一个一个的键值对,即<k1,v1>

 

综上:我们可以简单理解为FileInputFormat这个类是将文件file切分成split块,而TextInputFormat这个类是负责将每一行记录解析为键值对<k1,v1>。

 

第二阶段:

调用自己编写的map逻辑,将输入的键值对<k1,v1>变成<k2,v2>。在这里要注意:每一个键值对<k1,v1>都会调用一次map函数。

 

第三阶段:

按照一定的规则对输出的键值对<k2,v2>进行分区:分区的规则是针对k2进行的,比如说k2如果是省份的话,那么就可以按照不同的省份进行分区,同一个省份的k2划分到一个区。注意:默认分区的类是HashPartitioner类,这个类默认只分为一个区,因此Reducer任务的数量默认也是1。

源码:org.apache.hadoop.mapreduce.lib.partition.HashPartitioner

public class HashPartitioner<K, V> extends Partitioner<K, V> {

 

  /** Use {@link Object#hashCode()} to partition. */

  public int getPartition(K key, V value,

                          int numReduceTasks) {

    return (key.hashCode() &Integer.MAX_VALUE) % numReduceTasks;

  }

}

 

第四阶段:

对每个分区中的键值对进行排序。注意:所谓排序是针对k2进行的,v2是不参与排序的,如果要让v2也参与排序,需要自定义排序的类。

 

第五阶段:

排序完之后要进行分组,即相同key的value放到同一个集合当中,例如在WordCount程序中的<hello,{1,1}>执行的就是这个步骤,但是要注意:分组也是针对key进行的,经过分组完之后,就得到了我们熟悉的键值对<k2,v2s>.

 

第六阶段(可选):

对分组后的数据进行归约处理。通过归约处理键值对<k2,v2s>变成了<k2,v2>,经过这一阶段,传送到Reducer任务端的数据量会减少。但是规约的使用是有条件的,所以这一阶段是可以选择的。

 

Mapper任务处理完之后,就进入到了我们的Reducer阶段:

Reducer任务的执行过程可以分为3个阶段:

第一阶段:

对多个Mapper任务的输出,按照不同的分区,通过网络拷贝到不同的Reducer节点上进行处理,将数据按照分区拷贝到不同的Reducer节点之后,对多个Mapper任务的输出在进行合并,排序。例如:在WordCount程序中,若一个Mapper任务输出了<hello,{1,1}>,另外一个Mapper任务的输出为<hello,{1,1,1}>,经过这次合并之后变为<hello,{1,1,1,1,1}>。

 

第二阶段:

调用自己的reduce逻辑,将键值对<k2,v2s>变为<k3,v3>。在这里注意:每一个键值对<k2,v2s>都会调用一次reduce函数。

 

第三阶段:

将Reducer任务的输出保存到指定的文件中。

 

最后,我们举一个例子,并且实现自定义排序的功能。

 

原始数据为(\t分隔符):

1       1

2       2

3       3

4       5

4       7

4       6

8       9

6       8

6       4

 

Java源码为:

package com.dream.mapreduce;

 

import java.io.DataInput;

import java.io.DataOutput;

import java.io.IOException;

import java.net.URI;

import java.net.URISyntaxException;

 

import org.apache.hadoop.conf.Configuration;

import org.apache.hadoop.fs.FSDataInputStream;

import  org.apache.hadoop.fs.FileSystem;

import  org.apache.hadoop.fs.Path;

import  org.apache.hadoop.io.IOUtils;

import  org.apache.hadoop.io.LongWritable;

import  org.apache.hadoop.io.NullWritable;

import  org.apache.hadoop.io.Text;

import org.apache.hadoop.io.WritableComparable;

import org.apache.hadoop.mapreduce.Job;

import org.apache.hadoop.mapreduce.Mapper;

import org.apache.hadoop.mapreduce.Reducer;

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.mapreduce.lib.partition.HashPartitioner;

 

public class MyDefineSort {

        

         public static String path1 = "hdfs://gpmaster:9000/sourcedata.txt";

         publi cstatic String path2 = "hdfs://gpmaster:9000/targetDir";

 

         public static void main(String[] args) throws IOException,

                            URISyntaxException,ClassNotFoundException, InterruptedException {

                  

                   Configuration conf = new Configuration();

                  

                   FileSystem fileSystem = FileSystem.get(new URI(path1), conf);

                   //输出路径必须是不存在的

                   if(fileSystem.exists(new Path(path2)))

                   {

                            fileSystem.delete(newPath(path2), true);

                   }

                  

                   Jobjob = Job.getInstance(conf, "MyDefineSort");

                   job.setJarByClass(MyDefineSort.class);

                  

                   //编写MapReduce的运行步骤

                   FileInputFormat.setInputPaths(job,new Path(path1));

                   job.setInputFormatClass(TextInputFormat.class);

                   job.setMapperClass(MyMapper.class);

                   job.setMapOutputKeyClass(SortWritable.class);

                   job.setMapOutputValueClass(NullWritable.class);

                   job.setNumReduceTasks(1);

                   job.setPartitionerClass(HashPartitioner.class);

                   job.setReducerClass(MyReducer.class);

 

                   job.setOutputKeyClass(SortWritable.class);

                   job.setOutputValueClass(NullWritable.class);

 

                   FileOutputFormat.setOutputPath(job,new Path(path2));

                  

                   //提交任务

                   job.waitForCompletion(true);

                  

                   //查看结果

                   FSDataInputStream fr = fileSystem.open(newPath("hdfs://gpmaster:9000/targetDir/part-r-00000"));

                   IOUtils.copyBytes(fr,System.out, 1024, true);

         }

 

         public static class MyMapper extends Mapper<LongWritable, Text, SortWritable,NullWritable> {

                  

                   protected void map(LongWritable k1, Text v1, Context context)

                                     throws IOException, InterruptedException {

                           

                            String[] splited = v1.toString().split("\t");

                            String num1 = splited[0];

                            String num2 = splited[1];

                            SortWritable vector = new SortWritable(Long.parseLong(num1),Long.parseLong(num2));

                            context.write(vector,NullWritable.get());

                   }

         }

 

         public static class MyReducer extends Reducer<SortWritable, NullWritable,SortWritable, NullWritable> {

                  

                   protected void reduce(SortWritable k2, Iterable<NullWritable> v2s, Context context)

                                     throws IOException, InterruptedException {

                            for(NullWritable v2 : v2s) {

                                     context.write(k2,NullWritable.get());

                            }

                   }

         }

}

 

class SortWritable implements WritableComparable<Object> {

         Longnum1;

         Longnum2;

 

         public SortWritable() {

         }

 

         public SortWritable(long num1, long num2) {

                   this.num1= num1;

                   this.num2= num2;

         }

 

         //序列化

         publicvoid write(DataOutput fw) throws IOException

         {

                   fw.writeLong(num1);

                   fw.writeLong(num2);

         }

 

         public void readFields(DataInput fr) throws IOException {

                   this.num1= fr.readLong();

                   this.num2= fr.readLong();

         }

 

         public int compareTo(Object obj) {

                   SortWritable cc = (SortWritable) obj;

                   if(this.num1 != cc.num1)

                            //num1升序排列

                            return (int) (this.num1 - cc.num1);

                   else

                            //num2升序排列

                            return (int) (this.num2 - cc.num2);

 

         }

 

         public String toString() {

                   return this.num1 + "\t" + this.num2;

         }

}

 

将上面代码打包为一个jar包,比如MyDefineSort.jar,并上传到Hadoop环境上,运行如下代码:

hadoop jar MyDefineSort.jar  com.dream.mapreduce.MyDefineSort

输出的文件为:

hdfs dfs -ls hdfs://gpmaster:9000/targetDir/part-r-00000

 

结果内容为:

1       1

2       2

3       3

4       5

4       6

4       7

6       4

6       8

8       9

 

可以看出首先根据第一列升序排列,如果第一列相同时,再根据第二列升序排列。

0 0
原创粉丝点击