MapReduce学习

MapReduce

一 MapReduce计算模型

    1. MapReduce的思想

MapReduce 思想在生活中处处可见。或多或少都曾接触过这种思想。MapReduce 的思想核心是“ 分而治之”,适用于大量复杂的任务处理场景(大规模数据处理场景)。即使是发布过论文实现分布式计算的谷歌也只是实现了这种思想,而不是自己原创。

Map 负责“分”,即把复杂的任务分解为若干个“简单的任务”来并行处理。可以进行拆分的前提是这些小任务可以并行计算,彼此间几乎没有依赖关系。

Reduce 负责“合”,即对 map 阶段的结果进行全局汇总。这两个阶段合起来正是 MapReduce 思想的体现。

    1. MapReduce的计算过程

对于数据的处理在mapreduce阶段都是以键值对的形式存在。

数据首先以键值对(<k1,v1>)的形式进行map阶段,经过map阶段的处理,map极端输出的结果也是键值对(<k2,v2>),这时候进入reduce整合阶段,经过reduce处理后输出结果也是键值对的形式(<k3,v3>)。

二 MapReduce计算过程

2.1 MapTask阶段

HDFS中的/wordcount/input路径下有1.txt ,2.txt两个文件,大小分别为150M和80M。

数据开始进去maptask之前,在客户端会对文件进行逻辑切片,注意是逻辑上的操作。这里切片的大小适合HDFS存储文件时的block大小一致,都为128M(这里的逻辑切片的具体操作会在下文解释)。

       这里将这两个文件切分成3片。要注意,逻辑切片的个数就是后面执行maptask的个数。此后,每个切片将会由各个maptask进行操作。以maptask1为例进行说明。

保证待处理的文件不会被重复处理,也不会被遗漏。

       一开始,maptask会指定组件inputformat(实际上是其实现类TextInputFormat)去进行数据的拉取。这里maptask1拉取到spilt1。

*** TextInputFormat

使用linerecordread来读取文件内容。以行为单位取读取,所以这时候数据的键值对中键是该行的偏移量,值就是这一行内容。Linerecordread每读一行(组装好一个键值对)就调用一次我们自己写的map方法对数据进行处理。

数据在经过map处理后,有组件outputcollect收集起来,不会直接保存在磁盘上,因为如果直接保存在磁盘上会就行频繁进行io操作,效率极低。outputcollect会先将数据保存在一个内存缓冲区,叫做内存环形区。其实不是环形,结构类似于队列,数据从一端进入,另一端出。

*** 内存环形缓冲区:

这里的内存缓冲区默认大小是100M,当数据存储其大小的0.8(称为溢出比)时就开始使用一个单独的线程spiller将数据往磁盘写,这里的参数是可以设置的。要注意的一点是,在数据存到内存环形缓冲区之前可以进行分区操作,也就是设置reducetask的个数,默认是1。默认的分区规则是用键的哈希值和分区个数进行模运算。

当数据量在内存缓冲区达到溢出比的时候,数据就会被写到磁盘上,这时候会进行sort排序,默认是根据字典序排序。如果设置了combine,在数据被写到磁盘之前会对键相同的数据进行一个合并。这里要注意的是,是否需要combine要根据具体业务来确定,有时候combine的使用会得到错误的数据(比如求中位数)。当这一个maptask的数据处理完成后,会将这些临时文件合并成一个最终的文件。虽然合并了不同的分区,但是会使用索引来标识不同的分区。所以最终得到的文件是一个分区且排序的文件。

2.2 ReduceTask阶段

首先会有一个线程fetcher取拉取属于各个ReduceTask(根据Map阶段设置的分区来拉取),同样也是存储到内存中。这时候也会进行一个sort排序,默认是字典序排序。同时会将拉去到的各个分区的数据根据相同的键进行合并,但是这里的合并可以看作是一个全局的合并。也就是合并得到一个分区且排序的文件。然后通过我们自己写的reduce方法对数据进行处理。最后使用一个组件OutputFormat (实际上是其子类TextOutputFormat)将数据写到磁盘上去。

***合并:

merge 有三种形式:内存到内存;内存到磁盘;磁盘到磁盘。默认情况下第一种形式不启用。当内存中的数据量到达一定阈值,就启动内存到磁盘的 merge。与 map 端类似,这也是溢写的过程,这个过程中如果你设置有 Combiner,也是会启用的,然后在磁盘中生成了众多的溢写文件。第二种 merge 方式一直在运行,直到没有 map 端的数据时才结束,然后启动第三种磁盘到磁盘的 merge 方式生成最终的文件。

2.3 shuffle

Map阶段处理的数据如何传递给reduce阶段,是MapReduce框架中最关键的一个流程。这个流程就叫shuffle,shuffle不是组件,只是一个过程,一个概念。

    shuffle是Mapreduce 的核心,它分布在Mapreduce的map阶段和reduce阶段。一般把从Map产生输出开始到Reduc取得数据作为输入之前的过程称作shuffle。

1).Collect 阶段:将MapTask的结果输出到默认大小为100M的环形缓冲区,保存的是 key/value,Partition分区信息等。

2).Spill 阶段:当内存中的数据量达到一定的阀值的时候,就会将数据写入本地磁盘,在将数据写入磁盘之前需要对数据进行一次排序的操作,如果配置了 combiner,还会将有相同分区号和 key 的数据进行排序。

3).Merge 阶段:把所有溢出的临时文件进行一次合并操作,以确保一个MapTask 最终只产生一个中间数据文件。

4).Copy 阶段: ReduceTask 启动 Fetcher 线程到已经完成 MapTask 的节点上复制一份属于自己的数据,这些数据默认会保存在内存的缓冲区中,当内存的缓冲区达到一定的阀值的时候,就会将数据写到磁盘之上。

5).Merge 阶段:在 ReduceTask 远程复制数据的同时,会在后台开启两个线程对内存到本地的数据文件进行合并操作。

6).Sort 阶段:在对数据进行合并的同时,会进行排序操作,由于 MapTask阶段已经对数据进行了局部的排序,ReduceTask 只需保证 Copy 的数据的最终整体有效性即可。

Shuffle 中的缓冲区大小会影响到 mapreduce 程序的执行效率,原则上说,缓冲区越大,磁盘 io 的次数越少,执行速度就越快缓冲区的大小可以通过参数调整, 参数:io.sort.mb 默认 100M。

2.4 Partition组件

默认的分发规则为:根据 key 的 hashcode%reducetask 数来分发。所以:如果要按照我们自己的需求进行分组,则需要改写数据分发(分组)组件 Partitioner,自定义一个 CustomPartitioner 继承抽象类:Partitioner,然后在job 对象中,设置自定义 partitioner: job.setPartitionerClass(CustomPartitioner.class)

2.5 Combiner组件

每一个 map 都可能会产生大量的本地输出,Combiner 的作用就是对 map 端的输出先做一次合并,以减少在 map 和 reduce 节点之间的数据传输量,以提高网络 IO 性能,是 MapReduce 的一种优化手段之一。

 ***combiner 是 MR 程序中 Mapper 和 Reducer 之外的一种组件

 ***combiner 组件的父类就是 Reducer

 ***combiner 和 reducer 的区别在于运行的位置:

Combiner 是在每一个 maptask 所在的节点运行

Reducer 是接收全局所有 Mapper 的输出结果;

          combiner 的意义就是对每一个 maptask 的输出进行局部汇总,以减小网络传输量。

 具体实现步骤:

1、自定义一个 combiner 继承 Reducer,重写 reduce 方法。

2、在 job 中设置:job.setCombinerClass(CustomCombiner.class)

combiner 能够应用的前提是不能影响最终的业务逻辑,而且,combiner 的

输出 kv 应该跟 reducer 的输入 kv 类型要对应起来。但是不是map阶段都需要combiner,在不必要的场景使用combiner反而会造成错误的结果(比如求中位数)。

2.6 MapReduce的序列化

序列化(Serialization)是指把结构化对象转化为字节流。

反序列化(Deserialization)是序列化的逆过程。把字节流转为结构化对象。

当要在进程间传递对象或持久化对象的时候,就需要序列化对象成字节流,反之当要将接收到或从磁盘读取的字节流转换为对象,就要进行反序列化。

Java 的序列化(Serializable)是一个重量级序列化框架,一个对象被序列化后,会附带很多额外的信息(各种校验信息,header,继承体系…),不便于在网络中高效传输;所以,hadoop 自己开发了一套序列化机制( Writable),精简,高效。不用像 java 对象类一样传输多层的父子关系,需要哪个属性就传输哪个属性值,大大的减少网络传输的开销。Writable是Hadoop的序列化格式,hadoop定义了这样一个Writable接口。一个类要支持可序列化只需实现这个接口,并重写redeFileds(反序列化)以及write(序列化方法)方法即可。

这里需要注意的是:反序列化时,从流中读取到的各个字段的顺序应该与序列化时写出去的顺序保持一致。

如需要将自定义的 bean 放在 key 中传输,则还需要实现 comparable 接口,因为 mapreduce 框中的 shuffle 过程一定会对 key 进行排序,此时,自定义的bean 实现的接口应该是:WritableComparable。这是将序列化和用来排序的比较接口合二为一了。

compareTo 方法用于将当前对象与方法的参数进行比较。

如果指定的数与参数相等返回 0。

如果指定的数小于参数返回 -1。

如果指定的数大于参数返回 1。

例如:o1.compareTo(o2);

返回正数的话,当前对象(调用 compareTo 方法的对象 o1)要排在比较对

象(compareTo 传参对象 o2)后面,返回负数的话,放在前面。

三 MapReduce的并行度机制

3.1 Map阶段的并行度机制

MapTask的并行度指的是map阶段有多少个并行的task共同处理任务。map阶段的任务处理并行度,势必影响到整个 job的处理速度。那么,MapTask并行实例是否越多越好呢?其并行度又是如何决定呢?

一个 MapReducejob 的map阶段并行度由客户端在提交 job 时决定,即客户端提交 job 之前会对待处理数据进行 逻辑切片。切片完成会形成 切片规划 文件(job.split),每个逻辑切片最终对应启动一个 maptask。逻辑切片机制由 FileInputFormat 实现类的  getSplits()方法完成。

3.1.1FileInputFormat切片机制

FileInputFormat 中默认的切片机制:

A. 简单地按照文件的内容长度进行切片

B. 切片大小,默认等于 block 大小

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

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

file1.txt 320M

file2.txt 10M

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

file1.txt.split1—0M~128M

file1.txt.split2—128M~256M

file1.txt.split3—256M~320M

file2.txt.split1—0M~10M

 

FileInputFormat 中切片的大小的参数配置

在 FileInputFormat 中,计算切片大小的逻辑:

Math.max(minSize, Math.min(maxSize, blockSize));

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

minsize:默认值:1

配置参数: mapreduce.input.fileinputformat.split.minsize

maxsize:默认值:Long.MAXValue

配置参数:mapreduce.input.fileinputformat.split.maxsizeblocksize

因此,默认情况下, split size =block size,在 hadoop 2.x 中为 128M。

maxsize(切片最大值):参数如果调得比 blocksize 小,则会让切片变小,而且就等于配置的这个参数的。minsize (切片最小值):参数调的比 blockSize 大,则可以让切片变得比blocksize 还大。但是,不论怎么调参数,都不能让多个小文件“划入”一个  split 。还有个细节就是:当 bytesRemaining/splitSize > 1.1 不满足的话,那么最后所有剩余的会作为一个切片。从而不会形成例如 129M 文件规划成两个切片的局面。

3.2 Reduce阶段的并行度机制

reducetask 并行度同样影响整个 job 的执行并发度和执行效率,与 maptask的并发数由切片数决定不同,Reducetask 数量的决定是可以直接手动设置:job.setNumReduceTasks(4);如果数据分布不均匀,就有可能在 reduce 阶段产生数据倾斜。

注意: reducetask 数量并不是任意设置,还要考虑业务逻辑需求,有些情况下,需要计算全局汇总结果,就只能有 1 个 reducetask。

3.3 并行度总结

最好每个 task 的执行时间至少一分钟。

如果 job 的每个 map 或者 reduce task 的运行时间都只有 30-40 秒钟,那么就减少该 job 的 map 或者 reduce 数,每一个 task(map|reduce)的 setup 和加入到调度器中进行调度,这个中间的过程可能都要花费几秒钟,所以如果每个task 都非常快就跑完了,就会在 task 的开始和结束的时候浪费太多的时间。

此外,默认情况下,每一个 task 都是一个新的 JVM 实例,都需要开启和销毁的开销。在一些情况下,JVM 开启和销毁的时间可能会比实际处理数据的时间要消耗的长,配置 task 的 M JVM  重用可以改善该问题:

(mapred.job.reuse.jvm.num.tasks,默认是 1,表示一个 JVM 上最多可以顺序执行的 task 数目(属于同一个 Job)是 1。也就是说一个 task 启一个 JVM)如果 input 的文件非常的大,比如 1TB,可以考虑将 hdfs 上的每个 block

size 设大,比如设成 256MB 或者 512MB。

四 MapReduce参数优化

4.1资源相关参数

//以下参数是在用户自己的 MapReduce 应用程序中配置就可以生效

(1) mapreduce.map.memory.mb: 一个 Map Task 可使用的内存上限(单位:MB),默认为 1024。如果 Map Task 实际使用的资源量超过该值,则会被强制杀死。

(2) mapreduce.reduce.memory.mb: 一个 Reduce Task 可使用的资源上限(单位:MB),默认

为 1024。如果 Reduce Task 实际使用的资源量超过该值,则会被强制杀死。

(3) mapreduce.map.cpu.vcores: 每个 Maptask 可用的最多 cpu core 数目, 默认值: 1

(4) mapreduce.reduce.cpu.vcores: 每个 Reducetask 可用最多 cpu core 数目默认值: 1

(5) mapreduce.map.java.opts: Map Task 的 JVM 参数,你可以在此配置默认的 java heap

size 等参数, 例如:“-Xmx1024m -verbose:gc -Xloggc:/tmp/@taskid@.gc”(@taskid@会被 Hadoop 框架自动换为相应的 taskid), 默认值: “”

(6) mapreduce.reduce.java.opts: Reduce Task 的 JVM 参数,你可以在此配置默认的 java

heap size 等参数, 例如:“-Xmx1024m -verbose:gc -Xloggc:/tmp/@taskid@.gc”, 默认值: “”

 

//应该在 yarn 启动之前就配置在服务器的配置文件中才能生效

(1) yarn.scheduler.minimum-allocation-mb RM 中每个容器请求的最小配置,以 MB 为单

位,默认 1024。

(2) yarn.scheduler.maximum-allocation-mb RM 中每个容器请求的最大分配,以 MB 为单

位,默认 8192。

(3) yarn.scheduler.minimum-allocation-vcores  1

(4)yarn.scheduler.maximum-allocation-vcores 32

(5) yarn.nodemanager.resource.memory-mb 表示该节点上YARN可使用的物理内存总量,

默认是 8192(MB),注意,如果你的节点内存资源不够 8GB,则需要调减小这个值,而 YARN

不会智能的探测节点的物理内存总量。

 

//shuffle 性能优化的关键参数,应在 yarn 启动之前就配置好

(1) mapreduce.task.io.sort.mb 100 shuffle 的环形缓冲区大小,默认 100m

(2) mapreduce.map.sort.spill.percent 0.8 环形缓冲区溢出的阈值,默认 80%

4.2 容错相关参数

(1) mapreduce.map.maxattempts: 每个 Map Task 最大重试次数,一旦重试参数超过该值,则认为 Map Task 运行失败,默认值:4。

(2) mapreduce.reduce.maxattempts: 每个Reduce Task最大重试次数,一旦重试参数超过该值,则认为 Map Task 运行失败,默认值:4。

(3) mapreduce.map.failures.maxpercent: 当失败的 Map Task 失败比例超过该值,整个作业则失败,默认值为 0. 如果你的应用程序允许丢弃部分输入数据,则该该值设为一个大于 0 的值,比如 5,表示如果有低于 5%的 Map Task 失败(如果一个 Map Task 重试次数超过

mapreduce.map.maxattempts,则认为这个 Map Task 失败,其对应的输入数据将不会产生任何结果),整个作业扔认为成功。

(4) mapreduce.reduce.failures.maxpercent: 当失败的 Reduce Task 失败比例超过该值为,整个作业则失败,默认值为 0.

(5) mapreduce.task.timeout:如果一个task在一定时间内没有任何进入,即不会读取新的数据,也没有输出数据,则认为该 task 处于 block 状态,可能是临时卡住,也许永远会卡住。为了防止因为用户程序永远 block 不退出,则强制设置了一个超时时间(单位毫秒),默认是600000,值为 0 将禁用超时。。

4.3效率跟稳定性参数

( 1) mapreduce.map.speculative: 是否为 Map Task 打开推测执行机制,默认为 true, 如

果为 true,则可以并行执行一些 Map 任务的多个实例。

(2) mapreduce.reduce.speculative: 是否为 Reduce Task 打开推测执行机制,默认为 true

(3)mapreduce.input.fileinputformat.split.minsize: FileInputFormat做切片时最小切片大小,默认 1。

(5)mapreduce.input.fileinputformat.split.maxsize: FileInputFormat做切片时最大切片大小推测执行机制(Speculative Execution):它根据一定的法则推测出“拖后腿”的任务,并为这样的任务启动一个备份任务,让该任务与原始任务同时处理同一份数据,并最终选用最先成

功运行完成任务的计算结果作为最终结果。

五 MapReduce程序

以wordcount为例

Map:

Reduce:

Main方法:

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值