Hadoop中MapReduce中combine、partition、shuffle的作用,程序中的使用

InputFormat类:该类的作用是将输入的文件和数据分割成许多小的split文件,并将split的每个行通过LineRecorderReader解析成<Key,Value>,通过job.setInputFromatClass()函数来设置,默认的情况为类TextInputFormat,其中Key默认为字符偏移量,value是该行的值。

Map类:根据输入的<Key,Value>对生成中间结果,默认的情况下使用Mapper类,该类将输入的<Key,Value>对原封不动的作为中间按结果输出,通过job.setMapperClass()实现。实现Map函数。

Combine类:实现combine函数,该类的主要功能是合并相同的key键,通过job.setCombinerClass()方法设置,默认为null,不合并中间结果。实现map函数

Partitioner类: 该该主要在Shuffle过程中按照Key值将中间结果分成R份,其中每份都有一个Reduce去负责,可以通过job.setPartitionerClass()方法进行设置,默认的使用hashPartitioner类。实现getPartition函数

Reducer类:将中间结果合并,得到中间结果。通过job.setReduceCalss()方法进行设置,默认使用Reducer类,实现reduce方法。

OutPutFormat类:该类负责输出结果的格式。可以通过job.setOutputFormatClass()方法进行设置。默认使用TextOUtputFormat类,得到<Key,value>对。
note:hadoop主要是上面的六个类进行mapreduce操作,使用默认的类,处理的数据和文本的能力很有限,具体的项目中,用户通过改写这六个类(重载六个类),完成项目的需求。说实话,我刚开始学的时候,我怀疑过Mapreudce处理数据功能,随着学习深入,真的很钦佩mapreduce的设计,基本就二个函数,通过重载,可以完成所有你想完成的工作。

 

public  static void main(String[] args)throws IOException {
        Configuration conf = new Configuration();
        Job job = new Job(conf);
        job.setInputFormatClass(TextInputFormat.class);
        job.setMapperClass(Mapper.class);
        job.setCombinerClass(null);
        job.setPartitionerClass(HashPartitioner.class);
        job.setReducerClass(Reducer.class);
        job.setOutputFormatClass(TextOutFormat.class);
    }
}

概括:
combine和partition都是函数,中间的步骤应该只有shuffle!

1.combine
combine分为map端和reduce端,作用是把同一个key的键值对合并在一起,可以自定义的。
combine函数把一个map函数产生的<key,value>对(多个key,value)合并成一个新的<key2,value2>.将新的<key2,value2>作为输入到reduce函数中
这个value2亦可称之为values,因为有多个。这个合并的目的是为了减少网络传输。

具体实现是由Combine类。
实现combine函数,该类的主要功能是合并相同的key键,通过job.setCombinerClass()方法设置,默认为null,不合并中间结果。实现map函数
具体调用:(下图是调用reduce,合并map的个数)

难点:不知道这个reduce和mapreduce中的reduce区别是什么?
下面简单说一下:后面慢慢琢磨:
在mapreduce中,map多,reduce少。
在reduce中由于数据量比较多,所以干脆,我们先把自己map里面的数据归类,这样到了reduce的时候就减轻了压力。

这里举个例子:
map与reduce的例子
map理解为销售人员,reduce理解为销售经理。
每个人(map)只管销售,赚了多少钱销售人员不统计,也就是说这个销售人员没有Combine,那么这个销售经理就累垮了,因为每个人都没有统计,它需要统计所有人员卖了多少件,赚钱了多少钱。
这样是不行的,所以销售经理(reduce)为了减轻压力,每个人(map)都必须统计自己卖了多少钱,赚了多少钱(Combine),然后经理所做的事情就是统计每个人统计之后的结果。这样经理就轻松多了。所以Combine在map所做的事情,减轻了reduce的事情。
(这就是为什么说map的Combine干reduce的事情,相信你应该明白了)

public  static void main(String[] args)throws IOException {
        Configuration conf = new Configuration();
        Job job = new Job(conf);
        job.setInputFormatClass(TextInputFormat.class);
        job.setMapperClass(Mapper.class);
         job.setCombinerClass(reduce.class);
        job.setPartitionerClass(HashPartitioner.class);
        job.setReducerClass(Reducer.class);
        job.setOutputFormatClass(TextOutFormat.class);
    }
}

2.partition
partition是分割map每个节点的结果,按照key分别映射给不同的reduce,也是可以自定义的。这里其实可以理解归类。
我们对于错综复杂的数据归类。比如在动物园里有牛羊鸡鸭鹅,他们都是混在一起的,但是到了晚上他们就各自牛回牛棚,羊回羊圈,鸡回鸡窝。partition的作用就是把这些数据归类。只不过在写程序的时候,mapreduce使用哈希HashPartitioner帮我们归类了。这个我们也可以自定义。
 

        HashPartitioner是mapreduce的默认partitioner。计算方法是

which reducer=(key.hashCode() & Integer.MAX_VALUE) % numReduceTasks,得到当前的目的reducer。


下面在看该如何自定义,该如何调用:(下面便是自定义了一个Partition函数,红字部分是算法的核心,也就是分区的核心)

public static class Partition extends Partitioner<IntWritable, IntWritable> {
                @Override
                public int getPartition(IntWritable key, IntWritable value,
                                int numPartitions) {
                        int Maxnumber = 65223;
                        int bound = Maxnumber / numPartitions + 1;
                        int keynumber = key.get();
                        for (int i = 0; i < numPartitions; i++) {
                                if (keynumber < bound * i && keynumber >= bound * (i - 1)) {
                                        return i - 1;
                                }
                        }
                        return 0;
                }

        }


那么我们该如何调用:(下面调用之后,你的分区函数就生效了)
 

public static void main(String[] args) throws IOException,
InterruptedException, ClassNotFoundException {
Configuration conf = new Configuration();
Job job = new Job(conf, "sort");
job.setJarByClass(Sort.class);
job.setMapperClass(Map.class);
job.setReducerClass(Reduce.class);
job.setPartitionerClass(Partition.class);
job.setOutputKeyClass(IntWritable.class);
job.setOutputValueClass(IntWritable.class);
FileInputFormat.setInputPaths(job, "/home/asheng/hadoop/in");
FileOutputFormat
.setOutputPath(job, new Path("/home/asheng/hadoop/out"));
job.waitForCompletion(true);
}
}


3.shuffle

shuffle就是map和reduce之间的过程,包含了两端的combine和partition。它比较难以理解,因为我们摸不着,看不到它,它只是理论存在的,而且确实存在,它属于mapreduce的框架,编程的时候,我们用不到它,它属于mapreduce框架。详细可以看通过实例让你真正明白mapreduce---填空式、分布(分割)编程

3.1shuffle的作用是
Map的结果,会通过partition分发到Reducer上,Reducer做完Reduce操作后,通过OutputFormat,进行输出
shuffle阶段的主要函数是fetchOutputs(),这个函数的功能就是将map阶段的输出,copy到reduce 节点本地。

mapreduce是hadoop的核心之一,mapreduce经常让我们产生各种困惑,我们只是知道什么是map,什么是renduce,甚至我们已经熟悉了mapreduce编程,但是内部的原理还是不明白。下面在回帖中,给大家解决部分问题。更多问题有待挖掘。
1.Shuffle的定义是什么?
2.map task与reduce task的执行是否在不同的节点上?
3.Shuffle产生的意义是什么?
4.每个map task都有一个内存缓冲区,存储着map的输出结果,当缓冲区快满的时候需要将缓冲区的数据该如何处理?
5.在map task执行时,它是如何读取HDFS的?
6.读取的Split与block的对应关系可能是什么?
7.MapReduce提供Partitioner接口,它的作用是什么?
8.溢写是在什么情况下发生?
9.溢写是为什么不影响往缓冲区写map结果的线程?
10.当溢写线程启动后,需要对这80MB空间内的key做排序(Sort)。排序是MapReduce模型默认的行为,这里的排序也是对谁的排序?
11.哪些场景才能使用Combiner呢?
12.Merge的作用是什么?
13.reduce中Copy过程采用是什么协议?
14.reduce中merge过程有几种方式,与map有什么相似之处?
15.溢写过程中如果有很多个key/value对需要发送到某个reduce端去,那么如何处理这些key/value值

Shuffle的正常意思是洗牌或弄乱,可能大家更熟悉的是Java API里的Collections.shuffle(List)方法,它会随机地打乱参数list里的元素顺序。如果你不知道MapReduce里Shuffle是什么,那么请看这张图: 

这张是官方对Shuffle过程的描述。但我可以肯定的是,单从这张图你基本不可能明白Shuffle的过程,因为它与事实相差挺多,细节也是错乱的。后面我会具体描述Shuffle的事实情况,所以这里你只要清楚Shuffle的大致范围就成-怎样把map task的输出结果有效地传送到reduce端。也可以这样理解, Shuffle描述着数据从map task输出到reduce task输入的这段过程。 

        在Hadoop这样的集群环境中,大部分map task与reduce task的执行是在不同的节点上。当然很多情况下Reduce执行时需要跨节点去拉取其它节点上的map task结果。如果集群正在运行的job有很多,那么task的正常执行对集群内部的网络资源消耗会很严重。这种网络消耗是正常的,我们不能限制,能做的就是最大化地减少不必要的消耗。还有在节点内,相比于内存,磁盘IO对job完成时间的影响也是可观的。从最基本的要求来说,我们对Shuffle过程的期望可以有: 

  • 完整地从map task端拉取数据到reduce 端。
  • 在跨节点拉取数据时,尽可能地减少对带宽的不必要消耗。
  • 减少磁盘IO对task执行的影响。


        OK,看到这里时,大家可以先停下来想想,如果是自己来设计这段Shuffle过程,那么你的设计目标是什么。我想能优化的地方主要在于减少拉取数据的量及尽量使用内存而不是磁盘。 

        我的分析是基于Hadoop0.21.0的源码,如果与你所认识的Shuffle过程有差别,不吝指出。我会以WordCount为例,并假设它有8个map task和3个reduce task。从上图看出,Shuffle过程横跨map与reduce两端,所以下面我也会分两部分来展开。 

        先看看map端的情况,如下图: 

上图可能是某个map task的运行情况。拿它与官方图的左半边比较,会发现很多不一致。官方图没有清楚地说明partition, sort与combiner到底作用在哪个阶段。我画了这张图,希望让大家清晰地了解从map数据输入到map端所有数据准备好的全过程。 

        整个流程我分了四步。简单些可以这样说,每个map task都有一个内存缓冲区,存储着map的输出结果,当缓冲区快满的时候需要将缓冲区的数据以一个临时文件的方式存放到磁盘,当整个map task结束后再对磁盘中这个map task产生的所有临时文件做合并,生成最终的正式输出文件,然后等待reduce task来拉数据。 

        当然这里的每一步都可能包含着多个步骤与细节,下面我对细节来一一说明: 
1. 在map task执行时,它的输入数据来源于HDFS的block,当然在MapReduce概念中,map task只读取split。Split与block的对应关系可能是多对一,默认是一对一。在WordCount例子里,假设map的输入数据都是像“aaa”这样的字符串。 

2. 在经过mapper的运行后,我们得知mapper的输出是这样一个key/value对: key是“aaa”, value是数值1。因为当前map端只做加1的操作,在reduce task里才去合并结果集。前面我们知道这个job有3个reduce task,到底当前的“aaa”应该交由哪个reduce去做呢,是需要现在决定的。 

        MapReduce提供Partitioner接口,它的作用就是根据key或value及reduce的数量来决定当前的这对输出数据最终应该交由哪个reduce task处理。默认对key hash后再以reduce task数量取模。默认的取模方式只是为了平均reduce的处理能力,如果用户自己对Partitioner有需求,可以订制并设置到job上。 

        在我们的例子中,“aaa”经过Partitioner后返回0,也就是这对值应当交由第一个reducer来处理。接下来,需要将数据写入内存缓冲区中,缓冲区的作用是批量收集map结果,减少磁盘IO的影响。我们的key/value对以及Partition的结果都会被写入缓冲区。当然写入之前,key与value值都会被序列化成字节数组。 

        整个内存缓冲区就是一个字节数组,它的字节索引及key/value存储结构我没有研究过。如果有朋友对它有研究,那么请大致描述下它的细节吧。 

3. 这个内存缓冲区是有大小限制的,默认是100MB。当map task的输出结果很多时,就可能会撑爆内存,所以需要在一定条件下将缓冲区中的数据临时写入磁盘,然后重新利用这块缓冲区。这个从内存往磁盘写数据的过程被称为Spill,中文可译为溢写,字面意思很直观。这个溢写是由单独线程来完成,不影响往缓冲区写map结果的线程。溢写线程启动时不应该阻止map的结果输出,所以整个缓冲区有个溢写的比例spill.percent。这个比例默认是0.8,也就是当缓冲区的数据已经达到阈值(buffer size * spill percent = 100MB * 0.8 = 80MB),溢写线程启动,锁定这80MB的内存,执行溢写过程。Map task的输出结果还可以往剩下的20MB内存中写,互不影响。 

        当溢写线程启动后,需要对这80MB空间内的key做排序(Sort)。排序是MapReduce模型默认的行为,这里的排序也是对序列化的字节做的排序。 

        在这里我们可以想想,因为map task的输出是需要发送到不同的reduce端去,而内存缓冲区没有对将发送到相同reduce端的数据做合并,那么这种合并应该是体现是磁盘文件中的。从官方图上也可以看到写到磁盘中的溢写文件是对不同的reduce端的数值做过合并。所以溢写过程一个很重要的细节在于,如果有很多个key/value对需要发送到某个reduce端去,那么需要将这些key/value值拼接到一块,减少与partition相关的索引记录。 

        在针对每个reduce端而合并数据时,有些数据可能像这样:“aaa”/1, “aaa”/1。对于WordCount例子,就是简单地统计单词出现的次数,如果在同一个map task的结果中有很多个像“aaa”一样出现多次的key,我们就应该把它们的值合并到一块,这个过程叫reduce也叫combine。但MapReduce的术语中,reduce只指reduce端执行从多个map task取数据做计算的过程。除reduce外,非正式地合并数据只能算做combine了。其实大家知道的,MapReduce中将Combiner等同于Reducer。 

        如果client设置过Combiner,那么现在就是使用Combiner的时候了。将有相同key的key/value对的value加起来,减少溢写到磁盘的数据量。Combiner会优化MapReduce的中间结果,所以它在整个模型中会多次使用。那哪些场景才能使用Combiner呢?从这里分析,Combiner的输出是Reducer的输入,Combiner绝不能改变最终的计算结果。所以从我的想法来看,Combiner只应该用于那种Reduce的输入key/value与输出key/value类型完全一致,且不影响最终结果的场景。比如累加,最大值等。Combiner的使用一定得慎重,如果用好,它对job执行效率有帮助,反之会影响reduce的最终结果。 

4.  每次溢写会在磁盘上生成一个溢写文件,如果map的输出结果真的很大,有多次这样的溢写发生,磁盘上相应的就会有多个溢写文件存在。当map task真正完成时,内存缓冲区中的数据也全部溢写到磁盘中形成一个溢写文件。最终磁盘中会至少有一个这样的溢写文件存在(如果map的输出结果很少,当map执行完成时,只会产生一个溢写文件),因为最终的文件只有一个,所以需要将这些溢写文件归并到一起,这个过程就叫做Merge。Merge是怎样的?如前面的例子,“aaa”从某个map task读取过来时值是5,从另外一个map 读取时值是8,因为它们有相同的key,所以得merge成group。什么是group。对于“aaa”就是像这样的:{“aaa”, [5, 8, 2, …]},数组中的值就是从不同溢写文件中读取出来的,然后再把这些值加起来。请注意,因为merge是将多个溢写文件合并到一个文件,所以可能也有相同的key存在,在这个过程中如果client设置过Combiner,也会使用Combiner来合并相同的key。 

        至此,map端的所有工作都已结束,最终生成的这个文件也存放在TaskTracker够得着的某个本地目录内。每个reduce task不断地通过RPC从JobTracker那里获取map task是否完成的信息,如果reduce task得到通知,获知某台TaskTracker上的map task执行完成,Shuffle的后半段过程开始启动。 

        简单地说,reduce task在执行之前的工作就是不断地拉取当前job里每个map task的最终结果,然后对从不同地方拉取过来的数据不断地做merge,也最终形成一个文件作为reduce task的输入文件。见下图: 

如map 端的细节图,Shuffle在reduce端的过程也能用图上标明的三点来概括。当前reduce copy数据的前提是它要从JobTracker获得有哪些map task已执行结束,这段过程不表,有兴趣的朋友可以关注下。Reducer真正运行之前,所有的时间都是在拉取数据,做merge,且不断重复地在做。如前面的方式一样,下面我也分段地描述reduce 端的Shuffle细节: 
1.  Copy过程,简单地拉取数据。Reduce进程启动一些数据copy线程(Fetcher),通过HTTP方式请求map task所在的TaskTracker获取map task的输出文件。因为map task早已结束,这些文件就归TaskTracker管理在本地磁盘中。 

2. Merge阶段。这里的merge如map端的merge动作,只是数组中存放的是不同map端copy来的数值。Copy过来的数据会先放入内存缓冲区中,这里的缓冲区大小要比map端的更为灵活,它基于JVM的heap size设置,因为Shuffle阶段Reducer不运行,所以应该把绝大部分的内存都给Shuffle用。这里需要强调的是,merge有三种形式:1)内存到内存  2)内存到磁盘  3)磁盘到磁盘。默认情况下第一种形式不启用,让人比较困惑,是吧。当内存中的数据量到达一定阈值,就启动内存到磁盘的merge。与map 端类似,这也是溢写的过程,这个过程中如果你设置有Combiner,也是会启用的,然后在磁盘中生成了众多的溢写文件。第二种merge方式一直在运行,直到没有map端的数据时才结束,然后启动第三种磁盘到磁盘的merge方式生成最终的那个文件。 

3. Reducer的输入文件。不断地merge后,最后会生成一个“最终文件”。为什么加引号?因为这个文件可能存在于磁盘上,也可能存在于内存中。对我们来说,当然希望它存放于内存中,直接作为Reducer的输入,但默认情况下,这个文件是存放于磁盘中的。当Reducer的输入文件已定,整个Shuffle才最终结束。然后就是Reducer执行,把结果放到HDFS上。 
 

===========================Hadoop中Partition解析==========================

Map的结果,会通过partition分发到Reducer上,Reducer做完Reduce操作后,通过OutputFormat,进行输出,下面我们就来分析参与这个过程的类。

Mapper的结果,可能送到Combiner做合并,Combiner在系统中并没有自己的基类,而是用Reducer作为Combiner的基类,他们对外的功能是一样的,只是使用的位置和使用时的上下文不太一样而已。Mapper最终处理的键值对<key, value>,是需要送到Reducer去合并的,合并的时候,有相同key的键/值对会送到同一个Reducer那。哪个key到哪个Reducer的分配过程,是由Partitioner规定的。它只有一个方法,

  1. getPartition(Text key, Text value, int numPartitions)  

输入是Map的结果对<key, value>和Reducer的数目,输出则是分配的Reducer(整数编号)。就是指定Mappr输出的键值对到哪一个reducer上去。系统缺省的Partitioner是HashPartitioner,它以key的Hash值对Reducer的数目取模,得到对应的Reducer。这样保证如果有相同的key值,肯定被分配到同一个reducre上。如果有N个reducer,编号就为0,1,2,3……(N-1)

Reducer是所有用户定制Reducer类的基类,和Mapper类似,它也有setup,reduce,cleanup和run方法,其中setup和cleanup含义和Mapper相同,reduce是真正合并Mapper结果的地方,它的输入是key和这个key对应的所有value的一个迭代器,同时还包括Reducer的上下文。系统中定义了两个非常简单的Reducer,IntSumReducer和LongSumReducer,分别用于对整形/长整型的value求和。

Reduce的结果,通过Reducer.Context的方法collect输出到文件中,和输入类似,Hadoop引入了OutputFormat。OutputFormat依赖两个辅助接口:RecordWriter和OutputCommitter,来处理输出。RecordWriter提供了write方法,用于输出<key, value>和close方法,用于关闭对应的输出。OutputCommitter提供了一系列方法,用户通过实现这些方法,可以定制OutputFormat生存期某些阶段需要的特殊操作。我们在TaskInputOutputContext中讨论过这些方法(明显,TaskInputOutputContext是OutputFormat和Reducer间的桥梁)。OutputFormat和RecordWriter分别对应着InputFormat和RecordReader,系统提供了空输出NullOutputFormat(什么结果都不输出,NullOutputFormat.RecordWriter只是示例,系统中没有定义),LazyOutputFormat(没在类图中出现,不分析),FilterOutputFormat(不分析)和基于文件FileOutputFormat的SequenceFileOutputFormat和TextOutputFormat输出。

基于文件的输出FileOutputFormat利用了一些配置项配合工作,包括:
mapred.output.compress:是否压缩;
mapred.output.compression.codec:压缩方法;
mapred.output.dir:输出路径;
mapred.work.output.dir:输出工作路径。
FileOutputFormat还依赖于FileOutputCommitter,通过FileOutputCommitter提供一些和Job,Task相关的临时文件管理功能。如FileOutputCommitter的setupJob,会在输出路径下创建一个名为_temporary的临时目录,cleanupJob则会删除这个目录。
SequenceFileOutputFormat输出和TextOutputFormat输出分别对应输入的SequenceFileInputFormat和TextInputFormat。

代码实现:

  1. package org.apache.hadoop.examples;  
  2.   
  3. import java.io.IOException;  
  4. import java.util.*;  
  5. import org.apache.hadoop.fs.Path;  
  6. import org.apache.hadoop.conf.*;  
  7. import org.apache.hadoop.io.*;  
  8. import org.apache.hadoop.mapred.*;  
  9. import org.apache.hadoop.util.*;  
  10.   
  11. /** 
  12.  * 输入文本,以tab间隔 
  13.  * kaka    1       28 
  14.  * hua     0       26 
  15.  * chao    1 
  16.  * tao     1       22 
  17.  * mao     0       29      22 
  18.  * */  
  19.   
  20. //Partitioner函数的使用  
  21.   
  22. public class MyPartitioner {  
  23.     // Map函数  
  24.     public static class MyMap extends MapReduceBase implements  
  25.             Mapper<LongWritable, Text, Text, Text> {  
  26.         public void map(LongWritable key, Text value,  
  27.                 OutputCollector<Text, Text> output, Reporter reporter)  
  28.                 throws IOException {  
  29.             String[] arr_value = value.toString().split("\t");  
  30.             //测试输出  
  31. //          for(int i=0;i<arr_value.length;i++)  
  32. //          {  
  33. //              System.out.print(arr_value[i]+"\t");  
  34. //          }  
  35. //          System.out.print(arr_value.length);  
  36. //          System.out.println();         
  37.             Text word1 = new Text();  
  38.             Text word2 = new Text();  
  39.             if (arr_value.length > 3) {  
  40.                 word1.set("long");  
  41.                 word2.set(value);  
  42.             } else if (arr_value.length < 3) {  
  43.                 word1.set("short");  
  44.                 word2.set(value);  
  45.             } else {  
  46.                 word1.set("right");  
  47.                 word2.set(value);  
  48.             }  
  49.             output.collect(word1, word2);  
  50.         }  
  51.     }  
  52.       
  53.     public static class MyReduce extends MapReduceBase implements  
  54.             Reducer<Text, Text, Text, Text> {  
  55.         public void reduce(Text key, Iterator<Text> values,  
  56.                 OutputCollector<Text, Text> output, Reporter reporter)  
  57.                 throws IOException {  
  58.             int sum = 0;  
  59.             System.out.println(key);  
  60.             while (values.hasNext()) {  
  61.                 output.collect(key, new Text(values.next().getBytes()));      
  62.             }  
  63.         }  
  64.     }  
  65.   
  66.     // 接口Partitioner继承JobConfigurable,所以这里有两个override方法  
  67.     public static class MyPartitionerPar implements Partitioner<Text, Text> {  
  68.         /** 
  69.          * getPartition()方法的 
  70.          * 输入参数:键/值对<key,value>与reducer数量numPartitions 
  71.          * 输出参数:分配的Reducer编号,这里是result 
  72.          * */  
  73.         @Override  
  74.         public int getPartition(Text key, Text value, int numPartitions) {  
  75.             // TODO Auto-generated method stub  
  76.             int result = 0;  
  77.             System.out.println("numPartitions--" + numPartitions);  
  78.             if (key.toString().equals("long")) {  
  79.                 result = 0 % numPartitions;  
  80.             } else if (key.toString().equals("short")) {  
  81.                 result = 1 % numPartitions;  
  82.             } else if (key.toString().equals("right")) {  
  83.                 result = 2 % numPartitions;  
  84.             }  
  85.             System.out.println("result--" + result);  
  86.             return result;  
  87.         }  
  88.           
  89.         @Override  
  90.         public void configure(JobConf arg0)   
  91.         {  
  92.             // TODO Auto-generated method stub  
  93.         }  
  94.     }  
  95.   
  96.     //输入参数:/home/hadoop/input/PartitionerExample /home/hadoop/output/Partitioner  
  97.     public static void main(String[] args) throws Exception {  
  98.         JobConf conf = new JobConf(MyPartitioner.class);  
  99.         conf.setJobName("MyPartitioner");  
  100.           
  101.         //控制reducer数量,因为要分3个区,所以这里设定了3个reducer  
  102.         conf.setNumReduceTasks(3);  
  103.   
  104.         conf.setMapOutputKeyClass(Text.class);  
  105.         conf.setMapOutputValueClass(Text.class);  
  106.   
  107.         //设定分区类  
  108.         conf.setPartitionerClass(MyPartitionerPar.class);  
  109.   
  110.         conf.setOutputKeyClass(Text.class);  
  111.         conf.setOutputValueClass(Text.class);  
  112.   
  113.         //设定mapper和reducer类  
  114.         conf.setMapperClass(MyMap.class);  
  115.         conf.setReducerClass(MyReduce.class);  
  116.   
  117.         conf.setInputFormat(TextInputFormat.class);  
  118.         conf.setOutputFormat(TextOutputFormat.class);  
  119.   
  120.         FileInputFormat.setInputPaths(conf, new Path(args[0]));  
  121.         FileOutputFormat.setOutputPath(conf, new Path(args[1]));  
  122.   
  123.         JobClient.runJob(conf);  
  124.     }  
  125. }  

附加WordCount中Combiner的使用案例:

package com.sl.bigdatatest.mapreduce;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.StringTokenizer;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
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.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;

public class WordCount {

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

        if (args.length < 2) {
            System.err.println("Uage:<in> <out>");
            System.exit(2);
        }

        String inputPath = args[0];
        Path outputPath = new Path(args[1]);

        //1.configuration
        Configuration conf = new Configuration();
        URI uri = new URI("hdfs://192.168.0.200:9000");
        FileSystem fileSystem = FileSystem.get(uri, conf);

        if (fileSystem.exists(outputPath)) {
            boolean b = fileSystem.delete(outputPath, true);
            System.out.println("已存在目录删除:"+b);
        }

        //2.建立job
        Job job = Job.getInstance(conf, WordCount.class.getName());
        job.setJarByClass(WordCount.class);

        //3.输入文件
        FileInputFormat.setInputPaths(job, new Path(inputPath));

        //4.格式化输入文件
        job.setInputFormatClass(TextInputFormat.class);

        //5.map
        job.setMapperClass(MapWordCountTask.class);
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(LongWritable.class);

        //6.reduce
        job.setReducerClass(ReduceWordCountTask.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(LongWritable.class);

        /**指定本job使用combiner组件,组件所用的类为ReduceWordCountTask**/
        job.setCombinerClass(ReduceWordCountTask.class);

        //7.输出文件
        FileOutputFormat.setOutputPath(job, outputPath);

        //8.输出文件格式化
        job.setOutputFormatClass(TextOutputFormat.class);

        //9.提交给集群执行
        job.waitForCompletion(true);

    }

    public static class MapWordCountTask extends Mapper<LongWritable, Text, Text, LongWritable> {

        private Text k2 = new Text();
        private LongWritable v2 = new LongWritable();

        @Override
        protected void map(LongWritable key, Text value, Context context) throws Exception {
            String content = value.toString();
            StringTokenizer st = new StringTokenizer(content);
            while (st.hasMoreElements()) {
                k2.set(st.nextToken());
                v2.set(1L);
                context.write(k2, v2);
            }
        }
    }

    public static class ReduceWordCountTask extends Reducer<Text, LongWritable, Text, LongWritable> {

        private LongWritable v3 = new LongWritable();

        @Override
        protected void reduce(Text k2, Iterable<LongWritable> v2s,Context context) throws Exception {
            long sum = 0;
            for (LongWritable longWritable : v2s) {
                sum += longWritable.get();
                v3.set(sum);
            }
            context.write(k2, v3);
        }
    }
}

原文链接:https://blog.csdn.net/sl1992/article/details/53980826

https://blog.csdn.net/u013063153/article/details/72357560

猜你喜欢

转载自blog.csdn.net/onway_goahead/article/details/88988535