标准的MapReduce程序的基础由Mapper-Reducer-Driver三部分组成
一. Mapper类
1、继承org.apache.hadoop.mapreduce.Mapper类,设置四个泛型< KeyIn ValueIn KeyOut ValueOut>
KeyIn ValueIn一般都是LongWritable和Text不变(MapReduce默认读取文件的类型为.txt),后两个泛型是向context传递的KeyOut ValueOut类型。当输入Mapper的文件类型为序列化文件时, KeyIn ValueIn可以更改为序列化文件key-value对应的序列化类型
2、hadoop提供的序列化类型特点:实现WritableComparable接口,重写readFields()、write()、compareTo()方法。部分常用的序列化类型
IntWritable、LongWritable,FloatWritable、DoubleWritable、NullWritable
2、 继承Mapper类之后要重写map方法,默认情况下map方法的三个参数分别为:LongWritable(KeyIn),value(默认为一行数据),context(上下文变量)。
默认情况下Map Reduce程序将每一个文件切片后按照行读取。将一行的数据存储至value中传递给map方法,在map方法中对于每行数据根据自定义的正则表达式进行分割,分割成最小数据单元后将其按照一定的对应规则赋值给Key,Value,传递到Context对象中,以便于reduce程序进行下一步处理。值得注意的是,对于context对象来说只能接受hadoop提供的序列化类型的参数传递
3、继承mapper类之后还可以重写setUp(),cleanUp()两个方法。setUp方法可以理解为执行maptask任务之前程序进行的初始化操作。cleanUp方法可以理解为执行完所有maptask任务之后进行的收尾工作。我们可以根据其方法特性以及业务逻辑重写这两个方法。
二、Reducer类
1、一个标准的Reducer类需要继承org.apache.hadoop.mapreduce.Reducer类,设置四个泛型(KeyIn ValueIn KeyOut ValueOut):Reducer端的keyin-valuein数据类型与Mapper端的keyout-valueout数据类型相同。Reducerr端的keyout-valueout,则是代表了Reducer向context写入的数据类型。
2、继承Reducer类之后要重写reduce方法,reduce方法的三个参数与mapper端的keyout-valueout相关:第一个参数的数据类型为mapper端的keyout数据类型,第二个参数的数据类型为Iterable迭代器,第三个类型为context(上下文变量)。reducer将Context对象中的数据拉取过来,key相同的value值归为一组数据,存储至Iterable迭代器中,执行reduce函数时我们可以遍历该迭代器对key相同的一组value数据进行操作。
3、只有所有maptask任务执行结束之后才会开始执行reducetask任务,并且默认情况下reducetask只有一个,且在同一个reducetask任务中会根据key的个数执行多次reduce方法。
4、在所有reducetask任务执行结束之后,按Driver类中的设置,context内的数据会写到文件中。输出方式我们可以选择普通的txt文本方式,或者seq序列化文件方式,后文中会到。
三、Driver程序
1、在Driver程序中,我们需要实现的基础是创建Job实例以及“四三二一”
- 创建Job实例
Job job = Job.getInstance();
- 四:向Job对象中设置Mapper端的KeyOutValueOut泛型,Reducer端的keyOut-ValueOut
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
- 三:向Job对象设置Mapper、Reducer、Driver类
job.setJarByClass(xxxDriver.class);
job.setMapperClass(xxxMapper.class);
job.setReducerClass(xxxReducer.class);
- 二:设置MapReduce涉及文件的输入输出路径
Path inputPath = new Path("文件输入路径");
Path outputPath = new Path("文件输出路径");
- 一:提交Job任务
System.out.println(job.waitForCompletion(true));
2、在Driver类中,我们可以通过job.setxxx()的方式设置其他的任务属性,例如Sort、Grouping等等
四、小结
1、Mapper、Reducer、Driver可以以内部类的方式写在同一个类中。
2、根据文件数据量的大小,会生成多个maptask任务,当所有maptask任务执行结束之后,开会开始执行reducetask任务.默认情况下reducetask数量为1,但是可以通过job.setNumReduceTasks()设置。
3、在输入输出路径位置,最好先判断输出路径是否存在,如不存在就创建,存在就删除,因为在mapreduce中会按照输出目录自动创建文件夹和输出文件。
五、基础案例:wordCount
案例目的:统计数据格式为xxx xxx的文件中所有单词的数量,wordCount在mapReduce程序中的地位相当于helloword在java程序的地位
- Mapper
package MapReduce.WordCount;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
/**
* Mapper里有四种泛型
* keyin valuein keyout valueout
* 注意:所有的泛型一定是可以序列化的(与java中的序列化原理一样,表现形式不同)
* 文件作为mapper的输入,最小的数据单元以键值对的形式作为输出
* LongWritable:文件的偏移量
* 偏移量:能控制文件中每行数据开头的数字
* Text:文件每行的实际内容
* Text:输出时候的那个单词
* IntWritable:每个单词对应的数字1
*
* setUp:任务开始时,最先开始执行的程序,仅执行一次,相当于初始化一些内容。一般不重写
* map:每来一个键值对,都要执行一次map方法,划分最小数据单元的操作就在这里执行。一般需要重写
* cleanUp:任务结束时,最后执行的程序,仅执行一次,相当于做一些收尾工作。一般不重写
* run:涉及一些核心的内容,没有极特殊情况不重写
*/
public class WordCountMapper extends Mapper<LongWritable,Text,Text,IntWritable> {
Text outkey = new Text();
IntWritable outvalue = new IntWritable();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//把从文件读出的每行的内容变成我们熟悉的String类型
String line = value.toString();
//把每行内容划分成每个独立的单词
//split参数为正则表达式
//String[] words = line.split(" ");
String[] words = line.split("\t");
//循环遍历数组,把数组中的每个单词以(key,1)输出到reduce
for(String word:words){
//输出的一种简便写法
// context.write(new Text(word),new IntWritable(1));
//标准写法 分别设置key和value
outkey.set(word);
outvalue.set(1);
//把设置好的key和value发送到reduce
context.write(outkey,outvalue);
}
}
}
- Reducer
package MapReduce.WordCount;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
public class WordCountReducer extends Reducer<Text,IntWritable,Text,IntWritable> {
IntWritable outvalue = new IntWritable();
static int i =0;
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
//求和的初始值
System.out.println(i++);
int sum = 0;
//每一个单词 把带来的序列化数字进行相加求和
for (IntWritable value:values){
sum += value.get();
}
//设置当前单词的总和作为value
outvalue.set(sum);
//把最终结果(key,sum)写入文件
context.write(key,outvalue);
}
}
- Driver
package MapReduce.WordCount;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
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.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import java.net.URI;
public class WordCountDriver {
public static void main(String[] args) throws Exception {
//创建一个job的实例
Job job = Job.getInstance();
//4 设置4个输出泛型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
//3 设置三个类(Mapper,Reducer,Driver)
job.setJarByClass(WordCountDriver.class);
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
//2 设置两个路径(输入和输出)
Path inputPath = new Path("file:///E:/idea-workSpace/HaoopText/WordCount/input");
Path outputPath = new Path("file:///E:/idea-workSpace/HaoopText/WordCount/output");
//通过输出路径创建文件系统的流
FileSystem fs = FileSystem.get(new URI(outputPath.toString()),new Configuration());
//判断输出目录是否存在,存在就删除,不然下面会报错(输出路径已存在)
if (fs.exists(outputPath)){
fs.delete(outputPath, true);
}
FileInputFormat.setInputPaths(job,inputPath);
//输出文件将自动创建文件夹,所以不存在于文件系统中
FileOutputFormat.setOutputPath(job,outputPath);
//1 提交job任务
System.out.println(job.waitForCompletion(true));
}
}