一、程序框架
整个程序写一个class,在里面一般有三块:map、reduce、main;
主要继承Mapper类去定义map方法,继承Reducer类去定义reduce方法,设计main函数去设置输出
二、函数构造
1.InputFormat:
public
interface
InputFormat<K, V> {
InputSplit[] getSplits(JobConf job,
int
numSplits)
throws
IOException;
RecordReader<K, V> getRecordReader(InputSplit split,
JobConf job,
Reporter reporter)
throws
IOException;
}
这里由用户定义需要的输入文件的内容格式
getSplits:用于将所有输入数据分成numSplits个split(即InputSplit[]数组),每个split分片(并不是一定是个文件)交给一个map task任务去处理。特别地,在输入文件多,但每个文件都很小,基于每个输入文件至少产生一个map task的原则,就会产生很多map增加调度开销,作业变慢。
RecordReader:用于定义每个map task任务是怎么去读取一个split的数据(即<K,V>)。默认下,RecordReader是LineRecordReader,以每行偏移量定为key(存储的概念,即改行数据在磁盘空间上相对于文件开始的地址偏移),每行的数据定为value。
2.Map:
public static class Map extends Mapper<Object,Text,IntWritable,IntWritable>{}
继承Mapper类:
设置输入的<key,values>类型,Object是粗类,这里随便不使用输入的key;输出的<key,values>类型,IntWritable是数字类
public void map(K1 key,V1 value,OutputCollector<K2,V2> output,Reporter reporter) throws IOException{}
定义map方法:
设置输入的<key,values>类型,OutputCollector获取map()的输出结果,Reporter保存了当前task处理进度,一般只定义一个全局变量context来传递
3.Partitioner
继承Partitioner类去定义,各个map task产生的<key,value>交给哪个recude task处理。最好把相近节点,处理的数据平均分布,从而达到负载均衡和减少传输。
getPartition(K2 key,V2 value,int numPartitions)
返回<k2,v2>对应的reduce task ID。 如果不定义,默认使用hash函数去分配。
4.Combiner
用于使map/reduce间的数据传输量减小,提高性能。一般与Reduce相同。
5.Reduce:
public static class Reduce extends Reducer<IntWritable,IntWritable,IntWritable,IntWritable>{}
继承Reducer类:
设置接收的<key,values>类型,跟map的类型基本一致;输出的<key,values>类型,即最终输出
public void reduce(IntWritable key,Iterable<IntWritable> values,Context context) throws IOException,InterruptedException{}
定义reduce方法:
设置接收的<key,values>类型,跟map类型基本一致,有时候会定义values为数组,保存key相同的数据;context供读取,即最终输出
6.main:
通用代码段:
public static void main(String[] args) throws Exception{
Configuration conf = new Configuration();
conf.set("mapred.job.tracker", "192.168.47.141:9001");
String[] ioArgs=new String[]{"dedup_in","dedup_out"};
String[] otherArgs = new GenericOptionsParser(conf, ioArgs).getRemainingArgs();
if (otherArgs.length != 2){ //判断程序参数是否有两个:输入,输出
System.err.println("Usage: Data Deduplication <in> <out>");
System.exit(2);
}
Job job = new Job(conf, "Data Deduplication");
job.setJarByClass(Dedup.class);
//设置Map、Combine和Reduce处理类
job.setMapperClass(Map.class);
job.setCombinerClass(Reduce.class);
job.setReducerClass(Reduce.class);
//设置输出类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);//设置输入和输出目录
FileInputFormat.addInputPath(job, new Path(otherArgs[0]));
FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]));
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
三、案例代码例解
map:
设计变量去保存需要的数据:
private static Text line=new Text();
private static IntWritable data=new IntWritable();
处理数据,输出<key,values>
String line=value.toString(); // Text的value变量转为string 赋值给line
data.set(Integer.parseInt(line)); // 把line 转为Integer
context.write(data, new IntWritable(1)); //把data以key写入上下文变量,values值只是为了标识多个同key数据
context.write(line, new Text("")); //输出以line为key的数据,键值对<line,"">,任意的values
分割数据
String line = value.toString();
StringTokenizer tokenizerArticle = new StringTokenizer(line, "\n");//将value数据以行分隔,一般value只有一行
while (tokenizerArticle.hasMoreElements()) {//一般只循环了一次,相同key就多次
//将行数据value以空格分隔
StringTokenizer tokenizerLine = new StringTokenizer(tokenizerArticle.nextToken());
String strName = tokenizerLine.nextToken(); //取第一段
String strScore = tokenizerLine.nextToken();//取第二段
Text name = new Text(strName);
int scoreInt = Integer.parseInt(strScore);
context.write(name, new IntWritable(scoreInt));
}
reduce:
直接输出
context.write(key, new Text("")); 直接输出key和""
for(IntWritable val:values){ //遍历values数组,只是为了处理重复数据
context.write(linenum, key); // reduce自带排序,以key
linenum = new IntWritable(linenum.get()+1); // 设计行数,累加
}