大数据-MapReduce(一)
目录
本章节开始我们将学习hadoop的另一个功能组件,就是MapReduce,它主要负责计算功能。
每天进步一小点,加油鸭~
MapReduce编程
概念
MapReduce是一个分布式运算程序的编程框架,是用户开发“基于Hadoop的数据分析应用”的核心框架。MapReduce核心功能是将用户编写的业务逻辑代码和自带的默认组件整合成一个完整的分布式运算程序,并发运行在一个Hadoop集群上。
MapReduce的核心思想是“分而治之”,适用于大量复杂的任务处理场景(大规模数据处理场景)。
主要分为Map和Reduce两个阶段,Map负责“分”,即把复杂的任务分解为若干“简单的任务”来并行处理。可以进行拆分的前提是这些小任务可以并行计算,彼此之间几乎没有依赖关系。Reduce负责“合”,即对Map阶段的结果进行全局汇总。
例举一个生活中的例子,比如我们要数图书馆中的所有书籍,小明同学数1号书架上的书,小王同学数2号书架上的书,这就是Map阶段,人越多数的也越快。最后将每个人数的数字合并在一起就得到了所有书架上书的总和,这就是Reduce阶段。
编程步骤
Map阶段(2个步骤)
- 第一步:设置 inputFormat类,将数据切分成 key value对,输入到第二步中。
- 第二步:自定义Map逻辑,处理第一步输入的数据,然后转换成新的key value 对进行输出。
Shuffle阶段(4个步骤)
- 第三步:对输出的 key value 对 进行分区。相同的key的数据发送到同一个 Reduce 中,相同 key 合并,value 形成一个集合。
- 第四步:对不同分区的数据按照相同的key进行排序。
- 第五步:对排序后的数据进行规约(combine操作),降低数据的网络拷贝(可选步骤)。
- 第六步:对排序后的数据进行分组,分组的过程中将相同的 key 的 value 放到一个集合当中。
Reduce阶段(2个步骤)
- 第七步:对多个 Map 任务进行合并,排序,写入 Reduce 函数中自己定义的逻辑,对输入的 key value 对进行处理,转换成新的 key value 对 进行输出。
- 第八步:设置 outputformat 将输出的 key value 对 数据进行保存到文件中。
Hadoop 中常用的数据类型
hadoop没有沿用java当中基本的数据类型,而是自己进行封装了一套数据类型,其自己封装的类型与java的类型对应如下:
Java类型 | Hadoop Writable类型 |
Boolean | BooleanWritable |
Byte | ByteWritable |
Int | IntWritable |
Float | FloatWritable |
Long | LongWritable |
Double | DoubleWritable |
String | Text |
Map | MapWritable |
Array | ArrayWritable |
byte[] | BytesWritable |
MapReduce运行模式
- 本地模式
mapreduce程序是被提交给LocalJobRunner在本地以单进程的形式运行。而处理的数据及输出结果可以在本地文件系统,也可以在hdfs上。怎样实现本地运行?写一个程序,不要带集群的配置文件,本质是程序的conf中是否有mapreduce.framework.name=local以及yarn.resourcemanager.hostname=local参数。本地模式非常便于进行业务逻辑的debug,只要在IDE中断点即可。
本地模式运行代码设置
configuration.set("mapreduce.framework.name","local");
configuration.set("yarn.resourcemanager.hostname","local");
TextInputFormat.addInputPath(job,new Path("file:///D:\\input"));
TextOutputFormat.setOutputPath(job,new Path("file:///D:\\output"));
- 集群模式
将mapreduce程序提交给yarn集群,分发到很多的节点上并发执行,处理的数据和输出结果应该位于hdfs文件系统。提交集群的实现步骤是将程序打成JAR包,然后在集群的任意一个节点上用hadoop命令启动。
yarn jar hadoop_hdfs_operate-1.0-SNAPSHOT.jar com.hlbdx.hdfs.demo1.JobMain
Hadoop的序列化和反序列化
序列化:就是把内存中的对象,转换成字节序列(或其他数据传输协议)以便于存储到磁盘(持久化)和网络传输。
反序列化:就是将收到字节序列(或其他数据传输协议)或者是磁盘的持久化数据,转换成内存中的对象。
Java 的序列化(Serializable)是一个重量级序列化框架,一个对象被序列化后,会附带很多额外的信息(各种校验信息,header,继承体系…),不便于在网络中高效传输;所以,hadoop 自己开发了一套序列化机制(Writable),精简,高效。不用像 java 对象类一样传输多层的父子关系,需要哪个属性就传输哪个属性值,大大的减少网络传输的开销。
Writable是Hadoop的序列化格式,hadoop定义了这样一个Writable接口。 一个类要支持可序列化只需实现这个接口即可。另外Writable有一个子接口是WritableComparable,它既可实现序列化,也可以对key进行比较,实现排序。
实现序列化的步骤
-
必须实现Writable接口
-
反序列化时,需要反射调用空参构造函数,所以必须有空参构造。
InputFormat详解以及mapTask个数
- InputFormat详解
InputFormat是mapreduce当中用于处理数据输入的一个组件,是最顶级的一个抽象父类,主要用于解决各个地方的数据源的数据输入问题。其中InputFormat的UML类图可以通过IDEA进行查看。
- FileInputFormat常用类介绍
FileInputFormat类也是InputFormat的一个子类,如果需要操作hdfs上面的文件,基本上都是通过FileInputFormat类来实现的,可以通过FileInputFormat来实现各种格式的文件操作,FileInputFormat的子实现类的UML类图如下
类名 | 主要作用 |
TextInputFormat | 读取文本文件 |
CombineFileInputFormat | 在MR当中用于合并小文件,将多个小文件合并之后只需要启动一个mapTask进行运行 |
SequenceFileInputFormat | 处理SequenceFile这种格式的数据 |
KeyValueTextInputFormat | 通过手动指定分隔符,将每一条数据解析成为key,value对类型 |
NLineInputFormat | 指定数据的行数作为一个切片 |
FixedLengthInputFormat | 从文件中读取固定宽度的二进制记录 |
MapTask的数量以及文件的输入切片机制
- MapTask的个数决定
在运行MapReduce程序的时候,可以清晰的看到会有多个mapTask的运行。
那么maptask的个数究竟与什么有关?
可以通过MapReduce的源码进行查看mapTask的个数究竟是如何决定的。
在MapReduce当中,每个mapTask处理一个切片split的数据量,注意切片与block块的概念很像,但是block块是HDFS当中存储数据的单位,切片split是MapReduce当中每个MapTask处理数据量的单位。
MapTask并行度决定机制
- 数据块:Block是HDFS物理上把数据分成一块一块。
- 数据切片:数据切片只是在逻辑上对输入进行分片,并不会在磁盘上将其切分成片进行存储。
查看FileInputFormat的源码,里面getSplits的方法便是获取所有的切片,其中有个方法便是获取切片大小。
切片大小的计算公式:
Math.max(minSize, Math.min(maxSize, blockSize));
//默认值为1
mapreduce.input.fileinputformat.split.minsize=1
//默认值Long.MAXValue blockSize为128M
mapreduce.input.fileinputformat.split.maxsize= Long.MAXValue
由以上计算公式可以推算出split切片的大小刚好与block块相等。
如何控制mapTask的个数
如果需要控制maptask的个数,我们只需要调整maxSize和minsize这两个值,那么切片的大小就会改变,切片大小改变之后,mapTask的个数就会改变。
-
maxsize(切片最大值):参数如果调得比blockSize小,则会让切片变小,而且就等于配置的这个参数的值。
-
minsize(切片最小值):参数调的比blockSize大,则可以让切片变得比blockSize还大。
Partitioner详解
在mapreduce执行当中,有一个默认的步骤就是partition分区,分区的主要作用是将相同的数据发送到同一个reduceTask里面去,在mapreduce当中有一个抽象类叫做Partitioner,默认使用的实现类是HashPartitioner,可以通过HashPartitioner的源码,查看到分区的逻辑如下:
//默认Partitioner分区
public class HashPartitioner<K,V> extends Partitioner<K,V>{
public int getPartition( K key, V value ,int numReduceTasks){
return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;
}
}
默认分区是根据key的hashCode对ReduceTasks个数取模得到的。用户无法控制哪个key存储到哪个分区。
自定义Partitioner步骤
- 继承Partitioner重写getPartition()方法。
public class CustomPartitioner extends Partitioner<Text,FlowBean>{
@Override
public int getPartition(Text key,FlowBean value,int numPartitions){
//控制分区代码逻辑
... ...
return partition;
}
}
- 在Job驱动中,设置自定义Partitioner
job.setPartitionerClass(CustomPartitioner.class);
自定义Partition后,要根据自定义Partitioner的逻辑设置相应数量的ReduceTask
job.setNumReduceTasks(5);
排序
排序是MapReduce框架中最重要的操作之一。
MapTask和ReduceTask均会对数据按照key进行排序。该操作属于Hadoop的默认行为。任何应用程序中的数据均会被排序,而不管逻辑上是否需要。默认排序是按照字典顺序排序,且实现该排序的方法是快速排序。
对于MapTask,它会将处理的结果暂时放到环形缓冲区中,当环形缓冲区使用率达到一定阈值后,再对缓冲区中的数据进行一次快速排序,并将这些有序数据溢写到磁盘上,而当数据处理完毕后,它会对磁盘上所有文件进行归并排序。
对于ReduceTask,它从每个MapTask上远程拷贝相应的数据文件,如果文件大小超过一定阈值,则溢写磁盘上,否则存储在内存中。如果磁盘上文件数目达到一定阈值,则进行一次归并排序以生成一个更大文件;如果内存中文件大小或者数目超过一定阈值,则进行一次合并后将数据溢写到磁盘上。当所有数据拷贝完毕后,ReduceTask统一对内存和磁盘上的所有数据进行一次归并排序。
各种排序的分类:
1、部分排序
MapReduce根据输入记录的键对数据集排序。保证输出的每个文件内部有序。
2、全排序
最终输出结果只有一个文件,且文件内部有序。实现方式是只设置一个ReduceTask。但该方法在处理大型文件时效率极低,因为一台机器处理所有文件,完全丧失了MapReduce所提供的并行架构。
3、辅助排序
在Reduce端对key进行分组。应用于:在接收的key为bean对象时,想让一个或几个字段相同(全部字段比较不相同)的key进入到同一个reduce方法时,可以采用分组排序。
4、二次排序
在自定义排序过程中,如果compareTo中的判断条件为两个即为二次排序。
Combiner(合并)
- Combiner是MR程序中Mapper和Reducer之外的一种组件。
- Combiner组件的父类就是Reducer。
- Combiner和Reducer的区别在于运行位置不同
- Combiner是在每一个MapTask所在的节点运行;
- Reducer是接收全局所有Mapper的输出结果;
- Combiner的意义就是对每个MapTask的输出进行局部汇总,减少网络传输量。
- Combiner能够应用的前提是不能影响最终的业务逻辑,并且Combiner的输出KV需要和Reducer的输入KV类型相对应。
GroupingComparator(分组)
GroupingComparator是MapReduce当中reduce端的一个功能组件,主要的作用是决定哪些数据作为一组,调用一次reduce的逻辑,默认是每个不同的key,作为多个不同的组,每个组调用一次reduce逻辑,我们可以自定义GroupingComparator实现不同的key作为同一个组,调用一次reduce逻辑
分组排序步骤:
(1)自定义类继承WritableComparator
(2)重写compare()方法
此博文仅供学习参考,如有错误欢迎指正。
上一篇《大数据-HDFS(五)》
下一篇《大数据-MapReduce(二)》
希望对大数据相关技术感兴趣的友友们关注一下,大家可以一起交流学习哦~