MapReduce之combiner及partitioner
文章开始把我喜欢的这句话送个大家:这个世界上还有什么比自己写的代码运行在一亿人的电脑上更酷的事情吗,如果有那就是让这个数字再扩大十倍。
//Map()
class MyMapper extends Mapper//MyMapper 继承内部类Mapper
{ private Text word=new Text();
private IntWritable(1);
public void map(object key,Text value,Context context)//一行为一个key,value为text类型文本,context为上下文环境
String Tokenizer itr=new Sring Tokenizer (value,toString() )//Tokenizer为分词器将value 中的文本分为一个个单词, 放 在iterable容器当中。
while(itr.hasMoreTokens())
{word.set(itr.nextToken())
context.write(word,one)}
}
这样的过程完成之后可以实现Map()函数输出为类型存储在iterable容器中,并作为Reduce()的输入
//Reduce()
MyReduce extends Reduce
{ private IntWritable result=new IntWritable();//result 存放最终结果
for(IntWritable val:values)
{sum+=val.get();
result.set(sum);//统计求和把list求和
context.write(key,result);}// 写入上下文环境中去,实现Reduce()的输出为类型
}
MapReduce实际的处理过程可以理解为Input->Map->Sort->Combine->Partition->Reduce->Output。
(1)Input阶段 数据以一定的格式传递给Mapper,有TextInputFormat,DBInputFormat,SequenceFileFormat等可以使用,在Job.setInputFormat可以设置,也可以自定义分分片函数。
(2)Map阶段 对输入的key,value进行处理,即map(k1,v1) -> list(k2,v2),使用Job.setMapperClass进行设置。
(3) Sort阶段 对于Mapper的输出进行排序,使用Job.setOutputKeyComparatorClass进行设置,然后定义排序规则
(4) Combine阶段 这个阶段对于Sort之后有相同key的结果进行合并,使用Job.setCombinerClass进行设置,也可以自定义Combine Class类。
(5) Partition阶段 将Mapper的中间结果按照Key的范围划分为R份(Reduce作业的个数),默认使用HashPatitioner(key.hashCode() & Integer.MAX_VALUE) % numPartitions),也可以自定义划分的函数。使用Job.setPartitionClass进行设置。
(6) Reduce阶段 对于Mapper的结果进一步进行处理,Job.setReducerClass进行设置自定义的Reduce类。
(7) Output阶段 Reducer输出数据的格式。
MapReduce将作业的整个运行过程分为两个阶段:Map阶段和Reduce阶段
Map阶段由一定数量的Map Task组成
输入数据格式解析:InputFormat
输入数据处理:Mapper
数据分组:Partitioner
Reduce阶段由一定数量的Reduce Task组成
数据远程拷贝
数据按照key排序
数据处理:Reducer
数据输出格式:OutputFormat
Combiner
(1)Combiner最基本是实现本地key的聚合,对map输出的key排序,value进行迭代。
(2)Combiner其本质上就是一个reduce,使用combiner之后,先完成的map会在本地聚合,提升速度。
Combiner实现
public static class MyCombiner extends
Reducer {
protected void reduce(
Text key,
java.lang.Iterable values,
org.apache.hadoop.mapreduce.Reducer.Context context)
throws java.io.IOException, InterruptedException {
// 显示次数表示规约函数被调用了多少次,表示k2有多少个分组
System.out.println("Combiner输入分组<" + key.toString() + ",N(N>=1)>");
long count = 0L;
for (LongWritable value : values) {
count += value.get();
// 显示次数表示输入的k2,v2的键值对数量
System.out.println("Combiner输入键值对<" + key.toString() + ","
+ value.get() + ">");
}
context.write(key, new LongWritable(count));
// 显示次数表示输出的k2,v2的键值对数量
System.out.println("Combiner输出键值对<" + key.toString() + "," + count
+ ">");
};
}
partitioner
Map的结果,会通过partition分发到Reducer上,Reducer做完Reduce操作后,通过OutputFormat,进行输出.partition是分割map每个节点的结果,按照key分别映射给不同的reduce,也是可以自定义的。这里其实可以理解归类。
Mapper的结果,可能送到Combiner做合并,Combiner在系统中并没有自己的基类,而是用Reducer作为Combiner的基类,他们对外的功能是一样的,只是使用的位置和使用时的上下文不太一样而已。Mapper最终处理的键值对
/*
* 自定义Partitioner类
*/
public static class KpiPartitioner extends Partitioner {
@Override
public int getPartition(Text key, KpiWritable value, int numPartitions) {
// 实现不同的长度不同的号码分配到不同的reduce task中
int numLength = key.toString().length();
if (numLength == 11) {
return 0;
} else {
return 1;
}
}
}
/**
设置为打包运行,设置Partitioner为LiuPartitioner设置ReducerTask的个数为2
注意:分区的例子必须要设置为打成jar包运行!*/
public int run(String[] args) throws Exception {
// 定义一个作业
Job job = new Job(getConf(), "MyJob");
// 分区需要设置为打包运行
job.setJarByClass(MyLiuJob.class);
// 设置输入目录
FileInputFormat.setInputPaths(job, new Path(INPUT_PATH));
// 设置自定义Mapper类
job.setMapperClass(MyMapper.class);
// 指定的类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(KpiWritable.class);
// 设置Partitioner
job.setPartitionerClass(LiuPartitioner.class);
job.setNumReduceTasks(2);
// 设置自定义Reducer类
job.setReducerClass(MyReducer.class);
// 指定的类型
job.setOutputKeyClass(Text.class);
job.setOutputKeyClass(KpiWritable.class);
// 设置输出目录
FileOutputFormat.setOutputPath(job, new Path(OUTPUT_PATH));
// 提交作业
System.exit(job.waitForCompletion(true) ? 0 : 1);
return 0;
}
加油吧,程序员!