传送门:
大数据学习系列:Hadoop3.0苦命学习(一)
大数据学习系列:Hadoop3.0苦命学习(二)
大数据学习系列:Hadoop3.0苦命学习(三)
大数据学习系列:Hadoop3.0苦命学习(四)
大数据学习系列:Hadoop3.0苦命学习(五)
大数据学习系列:Hadoop3.0苦命学习(六)
大数据学习系列:Hadoop3.0苦命学习(七)
本节内容包括:MapReduce详解及两个实验
目录
1 MapReduce 介绍
MapReduce思想在生活中处处可见。或多或少都曾接触过这种思想。MapReduce的思
想核心是“分而治之”,适用于大量复杂的任务处理场景(大规模数据处理场景)。
-
Map 负责“分”,即把复杂的任务分解为若干个“简单的任务”来并行处理。可以进行拆
分的前提是这些小任务可以并行计算,彼此间几乎没有依赖关系。 -
Reduce 负责“合”,即对map阶段的结果进行全局汇总。
-
MapReduce 运行在yarn集群
- ResourceManager
- NodeManager
这两个阶段合起来正是MapReduce思想的体现。
还有一个比较形象的语言解释 MapReduce:
我们要数图书馆中的所有书。你数1号书架,我数2号书架。这就是“Map”。我们人越多,
数书就更快。
现在我们到一起,把所有人的统计数加在一起。这就是“Reduce”。
1.1 MapReduce 设计构思和框架结构
MapReduce 是一个分布式运算程序的编程框架,核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序,并发运行在Hadoop集群上。
既然是做计算的框架,那么表现形式就是有个输入(input),MapReduce操作这个输
入(input),通过本身定义好的计算模型,得到一个输出(output)。
Hadoop MapReduce构思:
- 分而治之
- 对相互间不具有计算依赖关系的大数据,实现并行最自然的办法就是采取分而治之的策略。并行计算的第一个重要问题是如何划分计算任务或者计算数据以便对
划分的子任务或数据块同时进行计算。不可分拆的计算任务或相互间有依赖关系
的数据无法进行并行计算! - 统一构架,隐藏系统层细节
- 如何提供统一的计算框架,如果没有统一封装底层细节,那么程序员则需要
考虑诸如数据存储、划分、分发、结果收集、错误恢复等诸多细节;为此,MapReduce设计并提供了统一的计算框架,为程序员隐藏了绝大多数系统
层面的处理细节。 - MapReduce 最大的亮点在于通过抽象模型和计算框架把需要做什么(what
need to do)与具体怎么做(how to do)分开了,为程序员提供一个抽象和高层的编程接口和框架。程序员仅需要关心其应用层的具体计算问题,仅需编写少量的处理应用本身计算问题的程序代码。如何具体完成这个并行计算任务所相关的诸多系统层细节被隐藏起来,交给计算框架去处理:从分布代码的执行,到大到数千小到单个节点集群的自动调度使用。
- 如何提供统一的计算框架,如果没有统一封装底层细节,那么程序员则需要
- 对相互间不具有计算依赖关系的大数据,实现并行最自然的办法就是采取分而治之的策略。并行计算的第一个重要问题是如何划分计算任务或者计算数据以便对
- 构建抽象模型: Map和Reduce
- MapReduce 借鉴了函数式语言中的思想,用Map和Reduce两个函数提供了高层
的并行编程抽象模型- Map: 对一组数据元素进行某种重复式的处理;
- Reduce: 对Map的中间结果进行某种进一步的结果整理。
- Map 和Reduce为程序员提供了一个清晰的操作接口抽象描述。MapReduce
处理的数据类型是键值对。
- MapReduce 中定义了如下的Map和Reduce两个抽象的编程接口,由用户去编程
实现:- Map: (k1; v1) → [(k2; v2)]
- Reduce: (k2; [v2]) → [(k3; v3)]
- MapReduce 借鉴了函数式语言中的思想,用Map和Reduce两个函数提供了高层
MapReduce 框架结构
一个完整的mapreduce程序在分布式运行时有三类实例进程:
MRAppMaster
负责整个程序的过程调度及状态协调MapTask
负责map阶段的整个数据处理流程ReduceTask
负责reduce阶段的整个数据处理流程
- 一种分布式计算模型,解决海量数据的计算问题
- MapReduce 将整个并行计算过程抽象到两个函数
- Map(映射):对一些独立元素组成的列表的每一个元素进行指定的操作,可以高度并行。
- Reduce(化简):对一个列表的元素进行合并。
- 一个简单的 MapReduce 程序只需要指定map()、reduce()、input 和 output,剩下的事由框架完成。
2 MapReduce 编程规范
MapReduce 的开发一共有八个步骤, 其中 Map 阶段分为 2 个步骤,Shuffle 阶段 4 个步骤,Reduce 阶段分为 2 个步骤
Map 阶段 2 个步骤
- 设置 InputFormat 类, 将数据切分为 Key-Value (K1和V1) 对, 输入到第二步
- 自定义 Map 逻辑, 将第一步的结果转换成另外的 Key-Value (K2和V2) 对, 输出结果
Shuffle 阶段 4 个步骤
- 对输出的 Key-Value 对进行分区
- 对不同分区的数据按照相同的 Key 排序
- (可选) 对分组过的数据初步规约, 降低数据的网络拷贝
- 对数据进行分组, 相同 Key 的 Value 放入一个集合中
Reduce 阶段 2 个步骤
- 对多个 Map 任务的结果进行排序以及合并, 编写 Reduce 函数实现自己的逻辑, 对输
入的 Key-Value 进行处理, 转为新的 Key-Value (K3和V3) 输出 - 设置 OutputFormat 处理并保存 Reduce 输出的 Key-Value 数据
3 WordCount
需求: 在一堆给定的文本文件中统计输出每一个单词出现的总次数
大致流程:
Step 1 数据格式准备
- 创建一个新的文件
cd /export/services
vim wordcount.txt
- 向其中放入以下内容并保存
hello,world,hadoop
hive,sqoop,flume,hello
kitty,tom,jerry,world
hadoop
- 上传到 HDFS
hdfs dfs -mkdir /wordcount/
hdfs dfs -put wordcount.txt /wordcount/
查看结果:
上传成功。
下面开始写代码,整体代码如下:
Step 2. Mapper
package cn.itcast.mapreduce;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
/*
Mapper的泛型:
KEYIN:k1的类型 行偏移量 LongWritable
VALUEIN:v1的类型 一行的文本数据 Text
KEYOUT:k2的类型 每个单词 Text
VALUEOUT:v2的类型 固定值1 LongWritable
*/
public class WordCountMapper extends Mapper<LongWritable, Text, Text, LongWritable> {
// key:是k1 value:是v1 context:表示MapReduce上下文对象
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
// 1. 对每一行字符串进行拆分
String line = value.toString();
String[] strings = line.split(",");
// 2. 遍历,获取每一个单词
for (String word: strings) {
context.write(new Text(word), new LongWritable(1));
}
}
}
Step 3. Reducer
package cn.itcast.mapreduce;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
/*
KEYIN:k2 Text 每个单词
VALUEIN:v2 LongWritable 集合中泛型的类型
KEYOUT:k3 Text 每个单词
VALUEOUT:k3 LongWritable 每个单词出现的次数
*/
public class WordCountReducer extends Reducer<Text, LongWritable, Text, LongWritable> {
/*
reduce方法的作用是将K2和V2转为K3和V3
key:K2
values:集合
context:MapReduce的上下文对象
*/
@Override
protected void reduce(Text key, Iterable<LongWritable> values, Context context) throws IOException, InterruptedException {
long count = 0;
// 1.遍历values集合
for (LongWritable value : values) {
// 2.将集合中的值相加
count += value.get();
}
// 3.将k3和v3写入上下文中
context.write(key, new LongWritable(count));
}
}
Step 4 定义主类, 描述 Job 并提交 Job
package cn.itcast.mapreduce;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
public class JobMain extends Configured implements Tool {
@Override
public int run(String[] strings) throws Exception {
// 创建一个任务对象
Job job = Job.getInstance(super.getConf(), "mapreduce_wordcount");
// 打包放在集群运行时,需要做一个配置
job.setJarByClass(JobMain.class);
// 第一步:设置读取文件的类:K1和V1
job.setInputFormatClass(TextInputFormat.class);
TextInputFormat.addInputPath(job, new Path("hdfs://node01:8020/wordcount"));
// 第二步:设置Mapper类
job.setMapperClass(WordCountMapper.class);
// 设置Map阶段的输出类型:k2和v2的类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(LongWritable.class);
// 第三、四、五、六步采用默认方式(分区,排序,规约,分组)
// 第七步:设置Reducer类
job.setReducerClass(WordCountReducer.class);
// 设置Reduce阶段的输出类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(LongWritable.class);
// 第八步:设置输出类
job.setOutputFormatClass(TextOutputFormat.class);
// 设置输出路径
TextOutputFormat.setOutputPath(job, new Path("hdfs://node01:8020/wordcount_out"));
boolean b = job.waitForCompletion(true);
return b?0:1;
}
public static void main(String[] args) throws Exception {
Configuration configuration = new Configuration();
// 启动一个任务
ToolRunner.run(configuration, new JobMain(), args);
}
}
运行实验
- 打包
- 在
pom.xml
中加上<packaging>jar</packaging>
- 点击
package
按钮进行打包
- 在
- 上传打包后的文件
- 通过
hadoop jar day03_mapreduce_wordcount-1.0-SNAPSHOT.jar cn.itcast.mapreduce.JobMain
启动程序
- 查看控制台是否有文件
- 下载该文件,查看其内容
成功!撒花!重大时刻~~~
4 MapReduce 分区
在 MapReduce 中, 通过我们指定分区, 会将同一个分区的数据发送到同一个 Reduce 当
中进行处理
例如: 为了数据的统计, 可以把一批类似的数据发送到同一个 Reduce 当中, 在同一个
Reduce 当中统计相同类型的数据, 就可以实现类似的数据分区和统计等
其实就是相同类型的数据, 有共性的数据, 送到一起去处理Reduce 当中默认的分区只有一个
4.1 修改代码
下面我们将在第4节的基础上进行修改
- 创建一个自定义的
Partitioner
package cn.itcast.mapreduce;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;
public class PartitionerOwn extends Partitioner<Text, LongWritable> {
// text: 表示K2 longWritable: 代表V2 i: reduce个数
@Override
public int getPartition(Text text, LongWritable longWritable, int i) {
// 如果单词的长度 >= 5 进入第一个分区
if (text.toString().length() >= 5) {
return 0;
}else{
return 1;
}
}
}
- 修改
JobMain
类中的代码,共改动两处- 设置分区的代码,
job.setPartitionerClass(PartitionerOwn.class);
- 设置Reduce的个数,
job.setNumReduceTasks(2);
- 设置分区的代码,
完整 JobMain
代码如下:
public class JobMain extends Configured implements Tool {
@Override
public int run(String[] strings) throws Exception {
// 创建一个任务对象
Job job = Job.getInstance(super.getConf(), "mapreduce_wordcount");
// 打包放在集群运行时,需要做一个配置
job.setJarByClass(JobMain.class);
// 第一步:设置读取文件的类:K1和V1
job.setInputFormatClass(TextInputFormat.class);
TextInputFormat.addInputPath(job, new Path("hdfs://node01:8020/wordcount"));
// 第二步:设置Mapper类
job.setMapperClass(WordCountMapper.class);
// 设置Map阶段的输出类型:k2和v2的类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(LongWritable.class);
// 第三、四、五、六步采用默认方式(分区,排序,规约,分组)
job.setPartitionerClass(PartitionerOwn.class);
// 第七步:设置Reducer类
job.setReducerClass(WordCountReducer.class);
// 设置Reduce阶段的输出类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(LongWritable.class);
// 设置Reduce的个数
job.setNumReduceTasks(2);
// 第八步:设置输出类
job.setOutputFormatClass(TextOutputFormat.class);
// 设置输出路径
TextOutputFormat.setOutputPath(job, new Path("hdfs://node01:8020/wordcount_out"));
boolean b = job.waitForCompletion(true);
return b?0:1;
}
public static void main(String[] args) throws Exception {
Configuration configuration = new Configuration();
// 启动一个任务
ToolRunner.run(configuration, new JobMain(), args);
}
}
4.2 WordCount分区实验
- 打包,上传
- 通过
hadoop jar day03_mapreduce_wordcount-1.0-SNAPSHOT.jar cn.itcast.mapreduce.JobMain
运行
执行后结果:
查看控制台:
可以看到有两部分文件,先打开上面的文件:
都是单词长度大于等于5的单词统计
再打开下面的文件:
成功!都是单词长度小于5的单词统计
5 MapReduce 排序和序列化
- 序列化 (Serialization) 是指把结构化对象转化为字节流
- 反序列化 (Deserialization) 是序列化的逆过程. 把字节流转为结构化对象. 当要在进程间传递对象或持久化对象的时候 , 就需要序列化对象成字节流, 反之当要将接收到或从磁盘读取的字节流转换为对象, 就要进行反序列化
- Java 的序列化 (Serializable) 是一个重量级序列化框架, 一个对象被序列化后, 会附带很多额外的信息 (各种校验信息, header, 继承体系等), 不便于在网络中高效传输. 所以, Hadoop 自己开发了一套序列化机制(Writable), 精简高效. 不用像 Java 对象类一样传输多层的父子关系, 需要哪个属性就传输哪个属性值, 大大的减少网络传输的开销
- Writable 是 Hadoop 的序列化格式, Hadoop 定义了这样一个 Writable 接口. 一个类
要支持可序列化只需实现这个接口即可 - 另外 Writable 有一个子接口是 WritableComparable, WritableComparable 是既可
实现序列化, 也可以对key进行比较, 我们这里可以通过自定义 Key 实现WritableComparable 来实现我们的排序功能
数据格式如下:
a 1
a 9
b 3
a 7
b 8
b 10
a 5
要求:
- 第一列按照字典顺序进行排列
- 第一列相同的时候 , 第二列按照升序进行排列
解决思路:
- 将 Map 端输出的 <key,value> 中的 key 和 value 组合成一个新的 key (newKey), value值不变
- 这里就变成 <(key,value),value> , 在针对 newKey 排序的时候, 如果 key 相同, 就再对value进行排序
实验总结构如下:
Step 1 自定义类型和比较器
package cn.itcast.mapreduce_sort;
import org.apache.hadoop.io.WritableComparable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public class PairWritable implements WritableComparable<PairWritable> {
private String first;
private int second;
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
public int getSecond() {
return second;
}
public void setSecond(int second) {
this.second = second;
}
// 实习排序规则
@Override
public int compareTo(PairWritable other) {
// 先比较first,如果first相同则比较second
int result = this.first.compareTo(other.first);
if (result == 0) {
return this.second - other.second;
}
return result;
}
@Override
public void write(DataOutput dataOutput) throws IOException {
dataOutput.writeUTF(first);
dataOutput.writeInt(second);
}
@Override
public void readFields(DataInput dataInput) throws IOException {
this.first = dataInput.readUTF();
this.second = dataInput.readInt();
}
@Override
public String toString() {
return first + '\t' + second ;
}
}
Step 2 Mapper
package cn.itcast.mapreduce_sort;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
public class SortMapper extends Mapper<LongWritable, Text, PairWritable, Text> {
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
// 1. 对每一行数据进行拆分,然后封装到PairWritable对象中,作为K2
String[] split = value.toString().split("\t");
PairWritable pairWritable = new PairWritable();
pairWritable.setFirst(split[0]);
pairWritable.setSecond(Integer.parseInt(split[1]));
// 2. 将K2和V2写入上下文中
context.write(pairWritable, value);
}
}
Step 3 Reducer
package cn.itcast.mapreduce_sort;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
public class SortReducer extends Reducer<PairWritable, Text, PairWritable, NullWritable> {
@Override
protected void reduce(PairWritable key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
for (Text text: values) {
context.write(key, NullWritable.get());
}
}
}
Step 4 Main 入口
package cn.itcast.mapreduce_sort;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
public class JobMain extends Configured implements Tool {
@Override
public int run(String[] strings) throws Exception {
Job job = Job.getInstance(super.getConf(), "mapreduce_sort");
// 打包放在集群运行时,需要做一个配置
job.setJarByClass(JobMain.class);
// 第一步:设置读取文件的类:K1和V1
job.setInputFormatClass(TextInputFormat.class);
TextInputFormat.addInputPath(job, new Path("hdfs://node01:8020/input/sort"));
// 第二步:设置Mapper类
job.setMapperClass(SortMapper.class);
// 设置Map阶段的输出类型:k2和v2的类型
job.setMapOutputKeyClass(PairWritable.class);
job.setMapOutputValueClass(Text.class);
// 第三、四、五、六步采用默认方式(分区,排序,规约,分组)
// 第七步:设置Reducer类
job.setReducerClass(SortReducer.class);
// 设置Reduce阶段的输出类型
job.setOutputKeyClass(PairWritable.class);
job.setOutputValueClass(NullWritable.class);
// 第八步:设置输出类
job.setOutputFormatClass(TextOutputFormat.class);
// 设置输出路径
TextOutputFormat.setOutputPath(job, new Path("hdfs://node01:8020/out/sort_out"));
boolean b = job.waitForCompletion(true);
return b?0:1;
}
public static void main(String[] args) throws Exception {
Configuration configuration = new Configuration();
int run = ToolRunner.run(configuration, new JobMain(), args);
System.exit(run);
}
}
运行实验
- 准备数据
- 打包jar包,上传
- 执行jar包,
hadoop jar day03_mapreduce_wordcount-1.0-SNAPSHOT.jar cn.itcast.mapreduce_sort.JobMain
查看结果: