调用API的思路:
(1) 用户编写的程序分成三个部分:Mapper,Reducer,Driver(提交运行 mr 程序的客户端)
(2)Mapper 的输入数据是 KV 对的形式(KV 的类型可自定义)
(3)Mapper 的输出数据是 KV 对的形式(KV 的类型可自定义)
(4)Mapper 中的业务逻辑写在 map()方法中
(5)map()方法(maptask 进程)对每一个<K,V>调用一次
(6)Reducer 的输入数据类型对应 Mapper 的输出数据类型,也是 KV
(7)Reducer 的业务逻辑写在 reduce()方法中
(8)Reducetask 进程对每一组相同 k 的<k,v>组调用一次 reduce()方法
(9)用户自定义的 Mapper 和 Reducer 都要继承各自的父类
(10)整个程序需要一个 Drvier 来进行提交,提交的是一个描述了各种必要信息的 job 对象
新建一个Maven工程
1、在pom.xml加入所需要的jar信息,由Maven自动解决依赖,其中<build>标签中添加了工具类,后期需要打成jar包。
<dependencies>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>2.8.2</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>2.8.2</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>2.8.2</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-core</artifactId>
<version>2.8.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>cn.itcast.mr.WordCountDriver</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
代码的文件树如下:
|--cn.itcast.mr
|----WordCountDriver
|----WordCountMapper
|----WordCountReducer
2、根据MapReduce处理流程,首先编写Map阶段(对应WordCountMapper)代码的注释已经非常详细
package cn.itcast.mr;
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;
/**
* 这里就是mapreduce程序 mapper阶段业务逻辑实现的类
*
* Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>
*
* KEYIN:表示mapper数据输入的时候key的数据类型,在默认的读取数据组件下,叫InputFormat,它的行为是一行一行的读取待处理的数据
* 读取一行,返回一行给我们的mr程序,这种情况下 keyin就表示每一行的起始偏移量 因此数据类型是Long
*
* VALUEIN:表述mapper数据输入的时候value的数据类型,在默认的读取数据组件下 valuein就表示读取的这一行内容 因此数据类型是String
*
* KEYOUT 表示mapper数据输出的时候key的数据类型 在本案例当中 输出的key是单词 因此数据类型是 String
*
* VALUEOUT表示mapper数据输出的时候value的数据类型 在本案例当中 输出的key是单词的次数 因此数据类型是 Integer
*
* 这里所说的数据类型String Long都是jdk自带的类型 在序列化的时候 效率低下 因此hadoop自己封装一套数据类型
* long---->LongWritable
* String-->Text
* Integer--->Intwritable
* null-->NullWritable
*
*
*/
public class WordCountMapper extends Mapper<LongWritable, Text,Text, IntWritable> {
/**
* 这里就是mapper阶段具体的业务逻辑实现方法 该方法的调用取决于读取数据的组件有没有给mr传入数据
* 如果有的话 每传入一个<k,v>对 该方法就会被调用一次
* @param key
* @param value
* @param context
* @throws IOException
* @throws InterruptedException
*/
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//拿到传入进来的一行内容,把数据类型转化为String
String line = value.toString();
//将这一行内容按照分隔符进行一行内容的切割 切割成一个单词数组
String[] words = line.split(" ");
//遍历数组,每出现一个单词 就标记一个数字1 <单词,1>
for (String word : words) {
//使用mr程序的上下文context 把mapper阶段处理的数据发送出去
//作为reduce节点的输入数据
context.write(new Text(word),new IntWritable(1));
//hadoop hadoop spark --> <hadoop,1><hadoop,1><spark,1>
}
}
}
3、编写Reduce阶段(对应WordCountReduce)
package cn.itcast.mr;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
/**
* 这里是MR程序 reducer阶段处理的类
*
* KEYIN:就是reducer阶段输入的数据key类型,对应mapper的输出key类型 在本案例中 就是单词 Text
*
* VALUEIN就是reducer阶段输入的数据value类型,对应mapper的输出value类型 在本案例中 就是单词次数 IntWritable
* .
* KEYOUT就是reducer阶段输出的数据key类型 在本案例中 就是单词 Text
*
* VALUEOUTreducer阶段输出的数据value类型 在本案例中 就是单词的总次数 IntWritable
*/
public class WordCountReducer extends Reducer<Text, IntWritable,Text,IntWritable> {
/**
* 这里是reduce阶段具体业务类的实现方法
* @param key
* @param values
* @param context
* @throws IOException
* @throws InterruptedException
*
* reduce接收所有来自map阶段处理的数据之后,按照key的字典序进行排序
* <hello,1><hadoop,1><spark,1><hadoop,1>
* 排序后:
* <hadoop,1><hadoop,1><hello,1><spark,1>
*
*按照key是否相同作为一组去调用reduce方法
* 本方法的key就是这一组相同kv对的共同key
* 把这一组所有的v作为一个迭代器传入我们的reduce方法
*
* <hadoop,[1,1]>
*/
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
//定义一个计数器
int count = 0;
//遍历一组迭代器,把每一个数量1累加起来就构成了单词的总次数
for(IntWritable value:values){
count +=value.get();
}
//把最终的结果输出
context.write(key,new IntWritable(count));
}
}
4、编写Job,定义一个主类,用来描述 job 并提交 job(对应WordCountDriver)
package cn.itcast.mr;
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.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
/**
* 这个类就是mr程序运行时候的主类,本类中组装了一些程序运行时候所需要的信息
* 比如:使用的是那个Mapper类 那个Reducer类 输入数据在那 输出数据在什么地方
*/
public class WordCountDriver {
public static void main(String[] args) throws Exception{
//通过Job来封装本次mr的相关信息
Configuration conf = new Configuration();
// conf.set("mapreduce.framework.name","local");
Job job = Job.getInstance(conf);
//指定本次mr job jar包运行主类
job.setJarByClass(WordCountDriver.class);
//指定本次mr 所用的mapper reducer类分别是什么
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
//指定本次mr mapper阶段的输出 k v类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
//指定本次mr 最终输出的 k v类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
// job.setNumReduceTasks(3);
/**如果业务有需求,就可以设置combiner组件(combiner是运行在Map到Reduce阶段中的一个组
件,可以帮助Reduce减轻负担,但是所适用的业务必须能够进行拆分,例如求中位数的业务就不可
以,本实例中可以添加提高IO性能)*/
//job.setCombinerClass(WordCountReducer.class);
//指定本次mr 输入的数据路径 和最终输出结果存放在什么位置
FileInputFormat.setInputPaths(job,"/count/input");
FileOutputFormat.setOutputPath(job,new Path("/count/output"));
// job.submit();
//提交程序 并且监控打印程序执行情况
boolean b = job.waitForCompletion(true);
System.exit(b?0:1);
}
}
注意:在WordCountDriver类中,我们需要指定使用的是哪个Mapper类、哪个Reducer类、输入数据位置、输出数据位置。 需要修改对应的代码,实例中输入文件在 /count/input 的HDFS文件中路径中,在运行之前需要在HDFS文件系统中提前新建好/count/input文件
5、在pom.xml找到如下代码标签,在此需要指定运行的主类(写全路径,在WordCountDriver类中,点击main, 右键->CopyReference ,然后粘贴即可)
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>cn.itcast.mr.WordCountDriver</mainClass>
</manifest>
6、打成jar包(为了在集群中运行,由yarn分配调度),打成jar包的方法,找到右上侧的Maven,进入Lifecycle->点击package,则在本工程文件下生成对应的Jar包文件。然后上传给Linux即可。
7、运行jar(在jar的文件下,运行此命令)-----集群模式
hadoop jar example-mr-1.1.jar
进入管理页面可以看到,自动生成了output文件夹
查看输出文件信息:
hadoop fs -cat /count/output/*
可见已经实现单词计数的功能: