1.1 MapReduce定义:

 

Mapreduce是一个分布式运算程序的编程框架,是用户开发“基于hadoop的数据分析应用”的核心框架。

 

Mapreduce核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序,并发运行在一个hadoop集群上。

 

 

1.2 MapReduce优缺点:

优点:

1.MapReduce 易于编程

它简单的实现一些接口,就可以完成一个分布式程序,这个分布式程序可以分布到大量廉价的PC机器上运行。也就是说你写一个分布式程序,跟写一个简单的串行程序是一模一样的。就是因为这个特点使得MapReduce编程变得非常流行。

2.良好的扩展性

当你的计算资源不能得到满足的时候,你可以通过简单的增加机器来扩展它的计算能力。

3.高容错性

MapReduce设计的初衷就是使程序能够部署在廉价的PC机器上,这就要求它具有很高的容错性。比如其中一台机器挂了,它可以把上面的计算任务转移到另外一个节点上运行,不至于这个任务运行失败,而且这个过程不需要人工参与,而完全是由Hadoop内部完成的。

4.适合PB级以上海量数据的离线处理

这里加红字体离线处理,说明它适合离线处理而不适合在线处理。比如像毫秒级别的返回一个结果,MapReduce很难做到。

缺点:

MapReduce擅长做实时计算、流式计算、DAG(有向图计算。

 

  1. 实时计算

 

MapReduce无法像Mysql一样,在毫秒或者秒级内返回结果。

 

  1. 流式计算

 

流式计算的输入数据是动态的,而MapReduce的输入数据集是静态的,不能动态变化。这是因为MapReduce自身的设计特点决定了数据源必须是静态的。

 

  1. DAG(有向图)计算

 

多个应用程序存在依赖关系,后一个应用程序的输入为前一个的输出。在这种情况下,MapReduce并不是不能做,而是使用后,每个MapReduce作业的输出结果都会写入到磁盘,会造成大量的磁盘IO,导致性能非常的低下。

 

1.3 MapReduce核心思想:

 

 

 

MapReduce 分布式缓存 滚动升级 mapreduce分布式计算框架_序列化

 

 

 

 

在MapReduce整个过程可以概括为以下过程:

 

输入

 

输入文件会被切分成多个块(默认128M),每一块都有一个map task

 

map阶段的输出结果会先写到内存缓冲区,然后由缓冲区写到磁盘上。默认的缓冲区大小是100M,溢出的百分比是0.8,也就是说当缓冲区中达到80M的时候就会往磁盘上写。如果map计算完成后的中间结果没有达到80M,最终也是要写到磁盘上的,因为它最终还是要形成文件。那么,在往磁盘上写的时候会进行分区(partition)和排序(sort)。一个map的输出可能有多个这个的文件,这些文件最终会合并成一个,这就是这个map的输出文件()。

MapReduce 分布式缓存 滚动升级 mapreduce分布式计算框架_序列化_02

 

 

 

流程说明如下:

1、输入文件分片,每一片都由一个MapTask来处理

2、Map输出的中间结果会先放在内存缓冲区中,这个缓冲区的大小默认是100M,当缓冲区中的内容达到80%时(80M)会将缓冲区的内容写到磁盘上。也就是说,一个map会输出一个或者多个这样的文件,如果一个map输出的全部内容没有超过限制,那么最终也会发生这个写磁盘的操作,只不过是写几次的问题。

3、从缓冲区写到磁盘的时候,会进行分区并排序,分区指的是某个key应该进入到哪个分区,同一分区中的key会进行排序,如果定义了Combiner的话,也会进行combine(合并)操作

4、如果一个map产生的中间结果存放到多个文件,那么这些文件最终会合并(mergeon)成一个文件,这个合并过程不会改变分区数量,只会减少文件数量。例如,假设分了3个区,4个文件,那么最终会合并成1个文件,3个区

5、以上只是一个map的输出,接下来进入reduce阶段

6、每个reducer对应一个ReduceTask,在真正开始reduce之前,先要从分区中抓取数据

7、相同的分区的数据会进入同一个reduce。这一步中会从所有map输出中抓取某一分区的数据,在抓取的过程中伴随着排序、合并。

8、reduce输出  

1分布式的运算程序往往需要分成至少2个阶段。

2第一个阶段的maptask并发实例,完全并行运行,互不相干。

3第二个阶段的reduce task并发实例互不相干,但是他们的数据依赖于上一个阶段的所有maptask并发实例的输出。

4MapReduce编程模型只能包含一个map阶段和一个reduce阶段,如果用户的业务逻辑非常复杂,那就只能多个mapreduce程序,串行运行。

1.4 MapReduce进程

一个完整的mapreduce程序在分布式运行时有三类实例进程:

1)MrAppMaster:负责整个程序的过程调度及状态协调。

2)MapTask:负责map阶段的整个数据处理流程。

3)ReduceTask:负责reduce阶段的整个数据处理流程。

1.5 MapReduce编程规范

用户编写的程序分成三个部分:Mapper、Reducer和Driver。

1.Mapper阶段

(1)用户自定义的Mapper要继承自己的父类

(2)Mapper的输入数据是KV对的形式(KV的类型可自定义)

(3)Mapper中的业务逻辑写在map()方法中

(4)Mapper的输出数据是KV对的形式(KV的类型可自定义)

(5)map()方法(maptask进程)对每一个<K,V>调用一次

2.Reducer阶段

(1)用户自定义的Reducer要继承自己的父类

(2)Reducer的输入数据类型对应Mapper的输出数据类型,也是KV

(3)Reducer的业务逻辑写在reduce()方法中

(4)Reducetask进程对每一组相同k的<k,v>组调用一次reduce()方法

3.Driver阶段

相当于yarn集群的客户端,用于提交我们整个程序到yarn集群,提交的是封装了mapreduce程序相关运行参数的job对象

 

 

Hadoop序列化:

 

2.1.1 什么是序列化

 

序列化就是把内存中的对象,转换成字节序列(或其他数据传输协议)以便于存储(持久化)和网络传输。

 

反序列化就是将收到字节序列(或其他数据传输协议)或者是硬盘的持久化数据,转换成内存中的对象。

 

2.1.2 为什么要序列化

 

一般来说,“活的”对象只生存在内存里,关机断电就没有了。而且“活的”对象只能由本地的进程使用,不能被发送到网络上的另外一台计算机。 然而序列化可以存储“活的”对象,可以将“活的”对象发送到远程计算机。

 

2.1.3 为什么不用Java的序列化

 

的序列化是一个重量级序列化框架(Serializable),一个对象被序列化后,会附带很多额外的信息(各种校验信息,header,继承体系等),不便于在网络中高效传输。所以,hadoop自己开发了一套序列化机制(Writable),特点如下:

 

1.紧凑

 

紧凑的格式能让我们充分利用网络带宽,而带宽是数据中心最稀缺的资源

 

2.快速

 

进程通信形成了分布式系统的骨架,所以需要尽量减少序列化和反序列化的性能开销,这是基本的;

 

3.可扩展

 

协议为了满足新的需求变化,所以控制客户端和服务器过程中,需要直接引进相应的协议,这些是新协议,原序列化方式能支持新的协议报文;

 

4.互操作

 

能支持不同语言写的客户端和服务端进行交互;

2.2 常用数据序列化类型:

 

表4-1 常用的数据类型对应的hadoop数据序列化类型

 

Java类型

Hadoop Writable类型

boolean

BooleanWritable

byte

ByteWritable

int

IntWritable

float

FloatWritable

long

LongWritable

double

DoubleWritable

String

Text

map

MapWritable

array

ArrayWritable

 

2.3 自定义bean对象实现序列化接口(Writable)

 

自定义bean对象要想序列化传输,必须实现序列化接口,需要注意以下7项。

 

(1)必须实现Writable接口

 

(2)反序列化时,需要反射调用空参构造函数,所以必须有空参构造

 

public FlowBean() {

super();

}

 

(3)重写序列化方法

 

@Override

public void write(DataOutput out) throws IOException {

out.writeLong(upFlow);

out.writeLong(downFlow);

out.writeLong(sumFlow);

}

 

(4)重写反序列化方法

 

@Override

public void readFields(DataInput in) throws IOException {

upFlow = in.readLong();

downFlow = in.readLong();

sumFlow = in.readLong();

}

 

(5)注意反序列化的顺序和序列化的顺序完全一致

 

(6)要想把结果显示在文件中,需要重写toString(),可用”\t”分开,方便后续用。

 

(7)如果需要将自定义的bean放在key中传输,则还需要实现comparable接口,因为mapreduce框中的shuffle过程一定会对key进行排序。

 

@Override

public int compareTo(FlowBean o) {

// 倒序排列,从大到小

return this.sumFlow > o.getSumFlow() ? -1 : 1;

}

 

 

 

MapReduce框架原理:

MapReduce 分布式缓存 滚动升级 mapreduce分布式计算框架_自定义_03

 


 

 

MapReduce 分布式缓存 滚动升级 mapreduce分布式计算框架_序列化_04

 


 

2.流程详解

上面的流程是整个mapreduce最全工作流程,但是shuffle过程只是从第7步开始到第16步结束,具体shuffle过程详解,如下:

1)maptask收集我们的map()方法输出的kv对,放到内存缓冲区中

2)从内存缓冲区不断溢出本地磁盘文件,可能会溢出多个文件

3)多个溢出文件会被合并成大的溢出文件

4)在溢出过程中,及合并的过程中,都要调用partitioner进行分区和针对key进行排序

5)reducetask根据自己的分区号,去各个maptask机器上取相应的结果分区数据

6)reducetask会取到同一个分区的来自不同maptask的结果文件,reducetask会将这些文件再进行合并(归并排序)

7)合并成大文件后,shuffle的过程也就结束了,后面进入reducetask的逻辑运算过程(从文件中取出一个一个的键值对group,调用用户自定义的reduce()方法)

3.注意

Shuffle中的缓冲区大小会影响到mapreduce程序的执行效率,原则上说,缓冲区越大,磁盘io的次数越少,执行速度就越快。

缓冲区的大小可以通过参数调整,参数:io.sort.mb  默认100M。

 

MapReduce 分布式缓存 滚动升级 mapreduce分布式计算框架_自定义_05

 


 

2.FileInputFormat源码解析(input.getSplits(job))

(1)找到你数据存储的目录。

(2)开始遍历处理(规划切片)目录下的每一个文件

(3)遍历第一个文件ss.txt

a)获取文件大小fs.sizeOf(ss.txt)

b)计算切片大小computeSpliteSize(Math.max(minSize,Math.min(maxSize,blocksize)))=blocksize=128M

c)默认情况下,切片大小=blocksize

d)开始切,形成第1个切片:ss.txt—0:128M 第2个切片ss.txt—128:256M 第3个切片ss.txt—256M:300M(每次切片时,都要判断切完剩下的部分是否大于块的1.1倍,不大于1.1倍就划分一块切片)

e)将切片信息写到一个切片规划文件中

f)整个切片的核心过程在getSplit()方法中完成

g)数据切片只是在逻辑上对输入数据进行分片,并不会再磁盘上将其切分成分片进行存储。InputSplit记录了分片的元数据信息,比如起始位置、长度以及所在的节点列表等

h)注意:block是HDFS物理上存储的数据,切片是对数据逻辑上的划分

(4)提交切片规划文件到yarn上,yarn上的MrAppMaster就可以根据切片规划文件计算开启maptask个数。

3.2.2切片机制

1.FileInputFormat中默认的切片机制

(1)简单地按照文件的内容长度进行切片

(2)切片大小,默认等于block大小

(3)切片时不考虑数据集整体,而是逐个针对每一个文件单独切片

比如待处理数据有两个文件:

file1.txt    320M

file2.txt    10M

经过FileInputFormat的切片机制运算后,形成的切片信息如下:

file1.txt.split1--  0~128

file1.txt.split2--  128~256

file1.txt.split3--  256~320

file2.txt.split1--  0~10M

2.FileInputFormat切片大小的参数配置

通过分析源码,在FileInputFormat中,计算切片大小的逻辑:Math.max(minSize, Math.min(maxSize, blockSize)); 

切片主要由这几个值来运算决定

mapreduce.input.fileinputformat.split.minsize=1 默认值为1

mapreduce.input.fileinputformat.split.maxsize= Long.MAXValue 默认值Long.MAXValue

因此,默认情况下,切片大小=blocksize。

maxsize(切片最大值):参数如果调得比blocksize小,则会让切片变小,而且就等于配置的这个参数的值。

minsize(切片最小值):参数调的比blockSize大,则可以让切片变得比blocksize还大。

3.获取切片信息API

// 根据文件类型获取切片信息

FileSplit inputSplit = (FileSplit) context.getInputSplit();

// 获取切片的文件名称

String name = inputSplit.getPath().getName();

3.2.3 CombineTextInputFormat切片机制

关于大量小文件的优化策略

默认情况下TextInputformat对任务的切片机制是按文件规划切片,不管文件多小,都会是一个单独的切片,都会交给一个maptask,这样如果有大量小文件,就会产生大量的maptask,处理效率极其低下。

 

 

1.优化策略

 

(1)最好的办法,在数据处理系统的最前端(预处理/采集),将小文件先合并成大文件,再上传到HDFS做后续分析。

 

(2)补救措施:如果已经是大量小文件在HDFS中了,可以使用另一种InputFormat来做切片(CombineTextInputFormat),它的切片逻辑跟TextFileInputFormat不同:它可以将多个小文件从逻辑上规划到一个切片中,这样,多个小文件就可以交给一个maptask。

 

(3)优先满足最小切片大小,不超过最大切片大小

 

CombineTextInputFormat.setMaxInputSplitSize(job, 4194304);// 4m

 

CombineTextInputFormat.setMinInputSplitSize(job, 2097152);// 2m

 

举例:0.5m+1m+0.3m+5m=2m + 4.8m=2m + 4m + 0.8m

 

3.2.5 InputFormat接口实现类

 

MapReduce任务的输入文件一般是存储在HDFS里面。输入的文件格式包括:基于行的日志文件、二进制格式文件等。这些文件一般会很大,达到数十GB,甚至更大。那么MapReduce是如何读取这些数据的呢?下面我们首先学习InputFormat接口。

 

InputFormat常见的接口实现类包括:TextInputFormat、KeyValueTextInputFormat、NLineInputFormat、CombineTextInputFormat和自定义InputFormat等。

 

1.TextInputFormat

 

TextInputFormat是默认的InputFormat。每条记录是一行输入。键是LongWritable类型,存储该行在整个文件中的起始字节偏移量。值是这行的内容,不包括任何行终止符(换行符和回车符)。

 

3.3工作机制

 

3.3.1并行度决定机制

 

1.问题引出

 

maptask的并行度决定map阶段的任务处理并发度,进而影响到整个job的处理速度。那么,mapTask并行任务是否越多越好呢?

 

2.MapTask并行度决定机制

 

一个job的map阶段MapTask并行度(个数),由客户端提交job时的切片个数决定,如图4-11所示。

MapReduce 分布式缓存 滚动升级 mapreduce分布式计算框架_数据_06

 


 

3.3.2 MapTask工作机制

MapReduce 分布式缓存 滚动升级 mapreduce分布式计算框架_数据_07

 

 

 

(1)Read阶段:Map Task通过用户编写的RecordReader,从输入InputSplit中解析出一个个key/value。

 

(2)Map阶段:该节点主要是将解析出的key/value交给用户编写map()函数处理,并产生一系列新的key/value。

 

(3)Collect收集阶段:在用户编写map()函数中,当数据处理完成后,一般会调用OutputCollector.collect()输出结果。在该函数内部,它会将生成的key/value分区(调用Partitioner),并写入一个环形内存缓冲区中。

 

(4)Spill阶段:即“溢写”,当环形缓冲区满后,MapReduce会将数据写到本地磁盘上,生成一个临时文件。需要注意的是,将数据写入本地磁盘之前,先要对数据进行一次本地排序,并在必要时对数据进行合并、压缩等操作。

 

溢写阶段详情:

 

步骤1:利用快速排序算法对缓存区内的数据进行排序,排序方式是,先按照分区编号partition进行排序,然后按照key进行排序。这样,经过排序后,数据以分区为单位聚集在一起,且同一分区内所有数据按照key有序。

 

步骤2:按照分区编号由小到大依次将每个分区中的数据写入任务工作目录下的临时文件output/spillN.out(N表示当前溢写次数)中。如果用户设置了Combiner,则写入文件之前,对每个分区中的数据进行一次聚集操作。

 

步骤3:将分区数据的元信息写到内存索引数据结构SpillRecord中,其中每个分区的元信息包括在临时文件中的偏移量、压缩前数据大小和压缩后数据大小。如果当前内存索引大小超过1MB,则将内存索引写到文件output/spillN.out.index中。

 

(5)Combine阶段:当所有数据处理完成后,MapTask对所有临时文件进行一次合并,以确保最终只会生成一个数据文件。

 

当所有数据处理完后,MapTask会将所有临时文件合并成一个大文件,并保存到文件output/file.out中,同时生成相应的索引文件output/file.out.index。

 

在进行文件合并过程中,MapTask以分区为单位进行合并。对于某个分区,它将采用多轮递归合并的方式。每轮合并io.sort.factor(默认100)个文件,并将产生的文件重新加入待合并列表中,对文件排序后,重复以上过程,直到最终得到一个大文件。

 

让每个MapTask最终只生成一个数据文件,可避免同时打开大量文件和同时读取大量小文件产生的随机读取带来的开销。

 

3.4 Shuffle机制

 

3.4.1 Shuffle机制

 

Mapreduce确保每个reducer的输入都是按key排序的。系统执行排序的过程(即将mapper输出作为输入传给reducer)称为shuffle,如图4-14所示。

MapReduce 分布式缓存 滚动升级 mapreduce分布式计算框架_数据_08

 


 

 


MapReduce理念:

计算向数据靠拢而不是数据向计算靠拢。

数据向计算靠拢:要完成一次数据分析时,选择一个计算节点,把运行数据分析的程序放到计算节点上运行,然后把它涉及的数据,全部从各个不同的节点上面拉过来,传输到计算发生的地方。

计算向数据靠拢:将应用程序分发到数据所在的机器。

Reduce的任务数量:最优的reduce任务个数取决于集群中可用的reduce任务槽(slot)的数目。