Hadoop MapReduce初窥-wordcount示例

来源:互联网 发布:多台docker php-fpm 编辑:程序博客网 时间:2024/05/16 15:46

MapReduce是一种可用于并行处理大规模集群上的海量数据的编程模型。它的出现使得并行处理海量数据变的更容易,容错性更高。本文借助wordcount程序介绍MapReduce的一些基本知识。本文在Eclipse环境中开发,然后编译成jar包,放单节点的伪集群中运行。

MapReduce程序由Mapper类,Reducer类以及一些用于运行作业的代码完成。map负责把任务分解成多个任务,reduce负责把分解后多任务处理的结果汇总起来。MapReduce框架处理键值对形式的数据,处理框架作业的输入是键值对形式的集合,处理后的输出也是同样形式,但并不要求输入和输出的数据类型是一样的。一个MapReduce作业的输入输出流程中各环节的数据形式如下:

(input) <k1, v1> -> map -> <k2, v2> -> combine -> <k2, v2> -> reduce -> <k3, v3> (output)

WordCount过程解析
Map阶段:首先对待处理的文件按照文件块(例如当前默认的256M)的大小进行切分,切分后的split作为Map任务的输入,每个split对应一个map任务进行并行处理(由此可见HDFS上过多的小文件,不仅会加重HDFS存储时NameNode的负担,也会造成MapReduce资源的调度的压力)。Map输入的key,默认为LongWritable,表示该行数据的起始位置相对于整个文件位置的偏移量。在本文中,并不关心该偏移量,故将其类型设置为Object,也不对其进行处理。例如,如果输入的文本内容如下:

hello hadoophello mapreduce

那么,map输入的key和value,如下:

(0,hello hadoop)  //key为0,是第一行文本的偏移量(12,hello mapreduce) //key为12,因为第二行跳过了第一行的12个字符

经过map阶段对每行文本进行单词切分,并将每个单词的计数置为1,输出如下:

(hello,1)(hadoop,1)(hello,1)(mapreduce,1)

Combiner阶段(非必须):会对map的输出进行本地的合并,减少网络的传输。

(hadoop,[1])(hello,[2]) //如果没有combiner,(hello,1)会分两次传输(mapreduce,[1])

Reduce阶段:对于map的结果进行合并,输入的key是单词,value是相同单词计数的列表。在数据从map到reduce的过程,还需经历shuffle阶段,此过程根据map输出的key进行重新排序和分组。根据排序和分组的结果,hadoop框架会决定reduce任务的分配。

代码示例:

package com.test.wordcount;import java.io.IOException;import java.util.regex.Pattern;import org.apache.hadoop.conf.Configuration;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.Mapper;import org.apache.hadoop.mapreduce.Reducer;import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;public class WordCount {    public static class TokenizerMapper extends Mapper<Object, Text, Text, IntWritable>{         //创建正则模式,"\\W+"表示所有非英文字母的字符        private static  final Pattern  splits = Pattern.compile("\\W+");          private final static IntWritable one = new IntWritable(1);        private Text word = new Text();        protected void setup(Context context)        {            //do something initial work        }        public void map(Object key, Text value, Context context) throws IOException, InterruptedException {            //以所有非英文字符作为分隔符,切分输入的值            String [] words = splits.split(value.toString());            for(int i = 0; i < words.length; ++i )            {                word.set(words[i]);      //调用Text类的方法,设置结果                context.write(word, one); //context实例用于将map处理结果以键值对的形式输出            }        }        protected void cleanup(Context context)        {            //do something clean up work        }    }    public static class IntSumReducer extends Reducer<Text,IntWritable,Text,IntWritable> {        private IntWritable result = new IntWritable();        public void reduce(Text key, Iterable<IntWritable> values,Context context ) throws IOException, InterruptedException {            int sum = 0;            for (IntWritable val : values) {                sum += val.get();            }            result.set(sum);            context.write(key, result);        }    }    public static void main(String[] args) throws Exception {        Configuration conf = new Configuration();        Job job = Job.getInstance(conf, "wordcount");        job.setJarByClass(WordCount.class);        job.setMapperClass(TokenizerMapper.class);        job.setCombinerClass(IntSumReducer.class);        job.setReducerClass(IntSumReducer.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);  }}

代码分析
Map

public static class TokenizerMapper extends Mapper<Object, Text, Text, IntWritable>{    //创建正则模式,"\\W+"表示所有非英文字母的字符    private static  final Pattern  splits = Pattern.compile("\\W+");    private final static IntWritable one = new IntWritable(1);    private Text word = new Text();    protected void setup(Context context)    {        //do something initial work    }    public void map(Object key, Text value, Context context) throws IOException, InterruptedException {        //以所有非英文字符作为分隔符,切分输入的值        String [] words = splits.split(value.toString());          for(int i = 0; i < words.length; ++i )        {            word.set(words[i]);  //调用Text类的方法,设置结果            context.write(word, one); //context实例用于将map处理结果以键值对的形式输出        }    }    protected void cleanup(Context context)    {        //do something clean up work    }}

map函数由Mapper实现,mapper类中声明了map()虚方法,此外还有run(),setup()cleanup()run()方法会根据上下文环境,判断是否还有输入,如果有,则不断的调用map函数进行处理。setup()用于在TokenizerMapper第一次实例化时,做一些必要的初始化工作;cleanup()则是在数据处理结束后提供需要的清理工作。当然,如果没有特别要初始化或者清理的对象,两者也没必要一定要实现。
Mapper的泛型参数[Object, Text, Text, IntWritable];分别是输入和输入的键值对,即Map的输入类型是[Object, Text],输出是[Text, IntWritable]。而Text,IntWritable等是实现了Writable接口,提供了Hadoop特有的序列化方式的类型,可以理解为int,String等类型在Hadoop中的包装。
Text类类似于Java中的String,IntWritable相当于int型。此外还有ObjectWritbale,NullWritable,ByteWritable,BooleanWritable,ShortWritable,FloatWritable,LongWritable,DoubleWritable等。此外还可以自定义Witebale类型,例如如下方式的自定义类型,readFields()和write()是必须要实现的方法:

public class MinMaxCountTuple implements Writable  {      private int Min ;      private int Max ;      MinMaxCountTuple(int min, int max)      {          //      }      @Override      public void readFields(DataInput in) throws IOException {          // TODO Auto-generated method stub          Min = in.readInt();          Max = in.readInt();      }      @Override      public void write(DataOutput out) throws IOException {          // TODO Auto-generated method stub          out.writeInt(Min);          out.writeInt(Max);      }      public String toString()      {          return "min:" + Min + " , max:" + Max;      }     }

map方法的调用Context内部类的context实例对键值对进行写入操作,该实例包含系统内部的上下文环境,用来存储Map方法产生的输出记录。

Reducer
Reducer函数由Reducer类实现,用于进一步处理map的输出。Reducer除了提供reduce方法外,同样提供了类似于mapper的run(),setup()cleanup()方法。Reducer的泛型参数[Text,IntWritable,Text,IntWritable]类似与mapper,分别表示Reducer的输入和输出的键值对类型,且Reducer的输入键值对类型[Text,IntWritable]对应于mapper的输出键值对类型。
reduce方法的输入参数是map的输出结果经过MapReducer框架混洗(shuffle)之后的结果,shuffle之后,相同的key会被规约到同一个reduce作业中,所以reduce的参数values是一个key的值列表。

public static class IntSumReducer extends Reducer<Text,IntWritable,Text,IntWritable> {    private IntWritable result = new IntWritable();    public void reduce(Text key, Iterable<IntWritable> values,Context context ) throws IOException, InterruptedException {        int sum = 0;        for (IntWritable val : values) {            sum += val.get();        }        result.set(sum);        context.write(key, result);    }}

Combiner
Combiner是一个可选的本地reducer,可以在map阶段聚合结果,从而减少map到reduce的网络传输代价,实现mapreduce性能的提升。例如如果同一个map中具有两个”hadoop”单词,如果没有Combiner,则map的输出到reduce接收前网络中会发送两次(“hadoop”,1),而有了Combiner之后,则可以在本地归约为(“hadoop”,2)进行一次网络传输。Combiner函数通常和Reducer的实现一样,但这只是在通常情况下,对于本例的计数是这样,因为在本地的合并不影响最终的结果(a+b = b+a);但是如果计算的是平均值,就不同了,(avg1 + agv2)/2 != 真实的平均值(avg1*m + avg2*n)/(m+n)

任务启动
获取当前系统的环境变量,并据此获得job实例,其中”wordcount”为该job的命名。通过job类可以配置输入/输出的数据格式,跟踪、控制整个任务的执行。

Configuration conf = new Configuration();Job job = Job.getInstance(conf, "wordcount");

setJarByClass()用于设置运行的jar包,hadoop利用传入的参数,查找包含它的jar文件

job.setJarByClass(WordCount.class);

FileInputFormat类的静态方法addInputPath用于新增mapreduce作业的输入目录
FileOutputFormat类的静态方法setOutputPath则定义mapreduce作业的输出保存路径

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

设置map,reduce等处理类:

job.setMapperClass(TokenizerMapper.class);job.setCombinerClass(IntSumReducer.class);job.setReducerClass(IntSumReducer.class);

设置输出的键值对类型:

job.setOutputKeyClass(Text.class);job.setOutputValueClass(IntWritable.class);

提交作业到集群
1.将代码打成jar file,export步骤中在如下的环节为jar选择main class;
这里写图片描述
2.准备输入数据

可以从本地拷贝任意文件,通过hadoop fs -copyFromLoacl 或者 hadoop fs -put 上传至hdfs

3.提交MR作业
hadoop jar wordcount.jar /import/data/wordcount/ /count
/import/data/wordcount/是待进行计算的输入目录,如果该目录下还有子目录,执行后将会抛出异常
/count是任务的结果路径,如果该路径已经在HDFS上存在,任务将无法提交;也就是说,只需指定结果输出路径,同时确保该路径在当前HDFS上不存在,Reduce会自动创建结果存放路径

4.MapReduce任务调度页面 http://{IP}:8099/
这里写图片描述

5.控制台输出

# hadoop jar wordcount.jar /import/data/wordcount/ /count17/08/12 11:37:35 INFO client.RMProxy: Connecting to ResourceManager at /0.0.0.0:8032...17/08/12 11:37:36 INFO input.FileInputFormat: Total input paths to process : 217/08/12 11:37:36 INFO mapreduce.JobSubmitter: number of splits:2...job_1502508379954_0002 running in uber mode : false17/08/12 11:37:46 INFO mapreduce.Job:  map 0% reduce 0%17/08/12 11:37:57 INFO mapreduce.Job:  map 100% reduce 0%17/08/12 11:38:03 INFO mapreduce.Job:  map 100% reduce 100%17/08/12 11:38:05 INFO mapreduce.Job: Job job_1502508379954_0002 completed successfully    Map-Reduce Framework    ...    Shuffle Errors    ...    File Input Format Counters         Bytes Read=156    File Output Format Counters         Bytes Written=81# hadoop fs -cat /count/*a   2compute 2data    2framework   2hadoop  2hello   4mapreduce   4mass    2test    2to  2
原创粉丝点击