一、MapReduce思想
核心思想是:分而治之。充分利用了并行处理的优势。
MapReduce任务过程是分为两个阶段:
Map阶段:Map阶段主要任务是“分”,把复杂的任务分解为若干个简单任务来并行处理。Map阶段这些任务可以并行计算,彼此之间没有依赖关系。
Reduce极端:Reduce阶段主要作用是“合”,即对map阶段的结果进行全局汇总。
二、官方wordcount案例源码解析
反编译 hadoop-mapreduce-examples-2.9.2.jar里面有个WordCount,里面包含了Mapper类、Reducer类、作业运行代码(Driver)
Mapper类继承了org.apache.hadoop.mapreduce.Mapper类重写了其中的map方法,
Reducer类继承了org.apache.hadoop.mapreduce.Reducer类重写了其中的reduce方法。
重写的Map方法作用:map方法其中的逻辑就是用户希望mr程序map阶段如何处理的逻辑。
重写的Reduce方法作用:reduce方法其中的逻辑是用户希望mr程序reduce阶段如何处理的逻辑。
hadoop的序列化:
序列化主要是我们通过网络通信传输数据时或者把对象持久化到文件,需要把对象序列化成二进制的结构。而在hadoop中序列化非常重要,集群中多个节点的进程间的通信是通过RPC(远程过程调度)实现。RPC将消息序列化成二进制发送至远程节点,远程节点接收到二进制数据反序列化成原始消息。
RPC往往追求如下特点:
紧凑:数据更紧凑,能够充分利用网络宽带资源。
快速:序列化和反序列化的性能开销更低。
hadoop使用的是自己的序列化格式writable,它比java的序列化serialization更紧凑更快速。一个对象serializable之后会携带很多额外信息,比如校验信息、Header、继承体等。
三、MapReduce编程规范及示例编写
3.1 Mapper类
自定义一个Mapper类继承hadoop的Mapper类
Mapper的输入数据是KV对形式(类型可以自定义)
Map阶段的业务逻辑在map()方法中
Mapper的输出数据是KV对形式(类型可以自定义)
注意:map()方法是输入一对KV调用一次!!!
3.2 Reducer类
自定义Reducer类继承hadoop的Reducer类
Reducer的输入数据类型对应Mapper的输出数据类型(KV对)
Reducer的业务逻辑在reduce()方法中
注意:reduce()方法是相同K的一组KV调用一次!!!
3.3 Driver类
创建提交YARN集群的Job对象。其中封装了MapReduce程序运行所需要的相关参数、输入数据路径、输出数据路径等,也相当于一个YARN集群的客户端,主要作用就是提交我们MapReduce程序运行。
3.4 WordCount代码自实现
/**
* 需求:单词计数
* 1.继承Mapper类
* 2.Mapper类泛型参数:共4个,2对KV
* 2.1 第一队KV是map出入参数类型 LongWritable, Text--->文本偏移量,一行文本内容
* 2.2 第二对KV是map输出参数类型 Text, IntWritable --->单词,1
*/
public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
private final Text word = new Text();
private final IntWritable one = new IntWritable(1);
/**
* 1.接受文本内容,转成String类型
* 2.按照空格进行拆分
* 3.输出单词
*/
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String str = value.toString();
String[] words = str.split(" ");
for (String s : words){
word.set(s);
context.write(word, one);
}
}
}
/**
* 第一对KV:类型要与Mapper输出类型一致:Text,IntWritable
* 第二对KV:自己设计决定输出的结果数据是什么类型:Text,IntWritable
*/
public class WordCountReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
private final IntWritable result = new IntWritable();
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
// 遍历values,然后累加结果
int sum = 0;
for (IntWritable value : values) {
sum += value.get();
}
result.set(sum);
context.write(key, result);
}
}
/**
* 1. 获取配置文件对象,获取job对象实例
* 2. 指定程序jar的本地路径
* 3. 指定Mapper/Reducer类
* 4. 指定Mapper输出的kv数据类型
* 5. 指定最终输出的kv数据类型
* 6. 指定job处理的原始数据路径
* 7. 指定job输出结果路径
* 8. 提交作业
*/
public class WordCountDriver {
public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException {
// 1. 获取配置文件对象,获取job对象实例
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
// 2. 指定程序jar的本地路径
job.setJarByClass(WordCountDriver.class);
// 3. 指定Mapper/Reducer类
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
// 4. 指定Mapper输出的kv数据类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
// 5. 指定最终输出的kv数据类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
// 6. 指定job处理的原始数据路径
FileInputFormat.setInputPaths(job, new Path(args[0]));
// 7. 指定job输出结果路径
FileOutputFormat.setOutputPath(job, new Path(args[1]));
// 8. 提交作业
boolean result = job.waitForCompletion(true);
// 退出JVM 0-正常退出,非0-异常
System.exit(result?0:1);
}
}
注意:本地idea运行mr任务与集群没有任何关系,没有提交任务到yarn集群,是在本地使用多线程
方式模拟的mr的运行。
接下来我们要做的就是让YARN集群去运行我们的代码
第一步:把代码打成jar包—》jar包放到linux上—》需要统计的wcinput.txt需要上传到hdfs上
以上就把该准备的准备好了。
第二部:运行:hadoop jar wc.jar com.lagou.mr.wc.WordCountDriver /usr/wordcount/wcinput.txt /usr/wordcount/wcoutput/
即可在wcoutput上看到结果。
四、序列化Writable接口
4.1 实现writable序列化步骤
必须实现Writable接口。
反序列化时需要反射调用空参构造函数,所以必须要有空参构造函数。
重写序列化方法。
重写反序列化方法。
序列化字段顺序必须和反序列化字段顺序一致。
如果自定义的bean要作为map输出KV的K,则该对象还需要实现Comparable接口,因为MapReduce中的Shuffle过程需要key必须能排序。
4.2 案例
需求:
代码:
package com.lagou.mr.writable;
import org.apache.hadoop.io.Writable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
/**
* 封装map方法的输出value:自有时长、第三方时长、总时长
*/
public class SpeakBean implements Writable {
private long selfDuration;
private long thirdDuration;
private long totalDuration;
// 重写序列化方法
@Override
public void write(DataOutput dataOutput) throws IOException {
dataOutput.writeLong(this.selfDuration);
dataOutput.writeLong(this.thirdDuration);
dataOutput.writeLong(this.totalDuration);
}
// 重写反序列化方法
@Override
public void readFields(DataInput dataInput) throws IOException {
this.selfDuration = dataInput.readLong();
this.thirdDuration = dataInput.readLong();
this.totalDuration = dataInput.readLong();
}
// 必须有一个无参构造
public SpeakBean() {
}
public SpeakBean(long selfDuration, long thirdDuration) {
this.selfDuration = selfDuration;
this.thirdDuration = thirdDuration;
this.totalDuration = this.selfDuration + this.thirdDuration;
}
// 省略get set toString方法....
}
package com.lagou.mr.writable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
/**
* 1.读取一行文本,按照制表符切分
* 2.抽取出自由内容时长、第三方内容时长、设备id
* 3.输出:key---设备id,value对象封装成一个bean对象携带自由内容时长、第三方内容时长、设备id
* 4.自定义bean对象作为value输出,需要实现writable接口
*/
public class SpeakDurationMapper extends Mapper<LongWritable, Text, Text, SpeakBean> {
private Text k = new Text();
private SpeakBean sb = new SpeakBean();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
// 1.获取一行
String line = value.toString();
// 2.切割字段
String[] fields = line.split("\t");
// 3.封装对象
String device_id = fields[1];
Long selfDuration = Long.parseLong(fields[4]);
Long thirdDuration = Long.parseLong(fields[5]);
sb.setSelfDuration(selfDuration);
sb.setThirdDuration(thirdDuration);
k.set(device_id);
// 4.输出
context.write(k, sb);
}
}
package com.lagou.mr.writable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
public class SpeakDurationReducer extends Reducer<Text, SpeakBean, Text, SpeakBean> {
@Override
protected void reduce(Text key, Iterable<SpeakBean> values, Context context) throws IOException, InterruptedException {
long selfDuration = 0;
long thirdDuration = 0;
// 遍历values 将自有、第三方时长累加
for (SpeakBean value : values) {
selfDuration += value.getSelfDuration();
thirdDuration += value.getThirdDuration();
}
// 封装对象
SpeakBean resultBean = new SpeakBean(selfDuration, thirdDuration);
// 输出
context.write(key, resultBean);
}
}
package com.lagou.mr.writable;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
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;
public class SpeakDriver {
public static void main(String[] args) {
Configuration cf = new Configuration();
try {
Job job = Job.getInstance(cf);
job.setJarByClass(SpeakDriver.class);
job.setMapperClass(SpeakDurationMapper.class);
job.setReducerClass(SpeakDurationReducer.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(SpeakBean.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
boolean res = job.waitForCompletion(true);
System.exit(res?0:1);
} catch (Exception e) {
e.printStackTrace();
}
}
}
五、MapReduce原理分析
5.1 MapTask运行机制详解
详细步骤:
a.读取数据组件InputFormat(默认TextInputFormat)会通过getSplits()方法对输入目录中文件进行逻辑切片规划得到splits,有多少个split就启动多少个maptask。spl