MapReduce执行流程

MapReduce

执行流程

MapTask执行流程

  1. Read:读取阶段

    1. MapTask会调用InputFormat中的getSplits方法来对文件进行切片

    2. 切片之后,针对每一个Split,产生一个RecordReader流用于读取数据

    3. 数据是以Key-Value形式来产生,交给map方法来处理。每一个键值对触发调用一次map方法

  2. Map:映射阶段

    1. map方法在获取到键值对之后,按照要求对键值对中的数据进行拆分解析,解析之后按照要求输出键值对形式的结果
  3. Collect:收集阶段

    1. MapTask拆分产生数据之后,并不是直接将数据传输给ReduceTask,而是会调用OutputCollector.collect方法来收集输出结果

    2. OutputCollector.collect在收集到数据之后,会先按照指定的规则,对数据进行分区,分区完成之后,会将数据写到缓冲区中

    3. 缓冲区本质上是一个环形的字节数组,默认大小是100M(可以通过属性mapreduce.task.io.sort.mb来调节),默认阈值是0.8(可以通过属性mapreduce.map.sort.spill.percent来调节)

  4. Spill:溢写阶段

    1. 当缓冲区使用达到指定阈值的时候,MapTask会将缓冲区中的数据冲刷(flush )到磁盘上,这个过程称之为溢写(spill)

    2. 在溢写的时候,会按照如下步骤进行

      1. 排序(sort )。此时,是将毫无规律的数据整理成有序数据,采用的Quick Sort(快速排序)。需要注意的是,数据在排序的时候,是按照分区内进行的排序,即先按照分区大小进行分区号的升序,然后每一个分区内按照指定规则排序。因此,数据是分区内有序

      2. 合并(combine )。如果用户指定了Combiner,那么此时会将数据进行合并处理

      3. 写出(flush )。按照分区的顺序,将每一个分区中的数据依次写入任务的工作目录的临时文件output/spillN.out中。N表示溢写次数。溢写次数不能完全由原始数据大小来决定,还得考虑map方法的处理过程。此时,单个结果文件中是分区且有序的,整体而言是局部有序

      4. 压缩(compress)。如果用户指定了对MapTask的结果进行压缩,那么数据在写出之后还会进行压缩处理

      5. 记录(record )。将分区数据的元信息记录到内存索引数据结果SpillRecord中。元信息中包含:每一个分区在每一个临时结果文件中的偏移量(offset ),每一个分区压缩前的数据大小以及压缩后的数据大小。如果SpillRecord中记录的所有的元信息大小之和不超过1M,那么SpillRecord中的数据也会写到output/spillN.out.index

  5. Merge:合并阶段

    1. 当MapTask将所有的数据处理完成之后,会将所有的临时结果文件spillN.out合并(merge )成一个大的结果文件output/file.out,同时会为这个文件生成索引文件file.out.index

    2. 在merge过程中,数据会再次进行分区。分区之后数据会再次进行排序。注意,此次排序是将局部有序的数据整理成整体有序,采用的是Merge Sort(归并排序)

    3. 在merge过程中,如果指定了Combiner,那么数据会进行combine操作

    4. merge的时候,默认情况下,是每10个(可以通过属性mapreduce.task.io.sort.factor来调节)小文件合并成1个大文件,通过多次合并,最后会产生一个结果文件file.out。这样子,能够有效的避免同时打开大量文件带来的开销

    5. 注意 :无论是否会进行Spill过程,最后都会产生一个file.out文件!!!

ReduceTask执行流程

  1. 当达到启动阈值的时候,ReduceTask就会启动。默认情况下,启动阈值是0.05,即5%的MapTask结束ReduceTask就会启动。可以通过属性mapreduce.job.reduce.slowstart.completedmaps来调节

  2. ReduceTask启动之后,会启动fetch 线程来抓取数据。默认情况下,每一个ReduceTask最多可以启动5个fetch线程来抓取数据。可以通过属性mapreduce.reduce.shuffle.parrellelcopies来调节

  3. fetch启动之后,会通过http请求的get请求来获取数据,在请求的时候,会携带参数表示当前的分区号。MapTask在收到请求之后,根据参数(分区号)来解析file.out.index文件,从中获取指定分区的位置,然后才会读取file.out,将对应分区的数据返回

  4. fetch线程在抓取到数据之后,会先对数据进行大小判断。如果数据超过了ReduceTask的缓冲区阈值,那么会将数据直接以文件形式写到磁盘上;如果没有超过ReduceTask的缓冲区阈值,那么就先放到缓冲区中

    1. 缓冲区的大小由属性mapreduce.reduce.shuffle.input.buffer.percent来决定,默认是0.7,是ReduceTask执行过程中允许占用内存的70%

    2. 缓冲区的阈值由属性mapreduce.reduce.shuffle.merge.percent来决定,默认是0.66,即缓冲区大小的66%

  5. fetch线程在抓取数据的同时,ReduceTask会启动两个后台线程将抓取来的数据进行merge,以防内存以及磁盘占用过多

  6. 所有的fetch线程完成之后,ReduceTask会将抓取来的所有的数据进行排序。同样,此时也是将局部有序的数据整理成整体有序的数据,所以依然采用的是归并排序(Merge Sort)

  7. 经过排序和merge,最终产生了一个大的临时 文件来交给ReduceTask来处理。此时,ReduceTask会将相同的键对应的值放到一起,形成一个伪迭代器(本质上就是一个流,来读取刚刚产生的临时文件),这个过程称之为分组(group)。也正因为是伪迭代器(将流包装成了迭代器,临时文件读取完之后就会被销毁),所以只能遍历一次!

  8. 分组完成之后,每一个键调用一次reduce方法,按照指定的规则来对数据进行聚合

  9. reduce处理完之后,会将结果传递给OutputFormat,按照指定规则将数据以指定形式写出到指定位置

流程图

常见优化方案

  1. 减少MapTask的溢写次数。溢写是将数据写到磁盘上,程序和磁盘交互次数越多,效率越低;此时,如果需要提高效率,就可以考虑减少MapTask和磁盘的交互次数

    1. 调节缓冲区的大小。通过mapreduce.task.io.sort.mb来调节,实际过程中,一般会将这个值调节为250M~400M

    2. 调节缓冲区阈值的大小。通过属性mapreduce.map.sort.spill.percent来调节

  2. 减少Merge次数。通过属性mapreduce.task.io.sort.factor来调节

  3. 增加Combiner。如果计算可以传递,那么建议在程序中使用Combiner。根据经验,使用Combiner,大约可以提升40%的效率

  4. 减少Reduce。如果MapTask处理完成之后,不需要使用Reduce聚合,那么此时可以直接省略Reduce

  5. 合理设置ReduceTask的执行内存。默认情况下,每一个ReduceTask最多占用1G内存,如果试图超过1G内存,就会被kill掉

    1. 调大ReducedTask的执行内存。通过mapreduce.reduce.memory.mb属性来调节,单位是MB,默认值是1024

    2. 调大缓冲区的占比。通过mapreduce.reduce.shuffle.input.buffer.percent属性来调节

    3. 调大缓冲区的阈值。通过mapreduce.reduce.shuffle.merge.percent属性来调节

  6. 增加fetch线程的数量。通过mapreduce.reduce.shuffle.parrellelcopies属性来调节

  7. 可以考虑对数据进行压缩。即将MapTask产生的结果进行压缩之后传递给ReduceTask,但是这种方案是在网络和解压效率之间进行了平衡/对比

其他

小文件问题

  1. 在大数据开发环境中,虽然实际处理的文件大部分都是大文件,但是依然无法避免产生小文件

  2. 一般而言,如果文件大小≤Block*0.8,那么此时就认为这是一个小文件。实际过程中,一般认为不超过100M的文件就是小文件

  3. 小文件在分布式环境下的问题

    1. 存储:在HDFS中,每一个小文件对应一条元数据,如果存储大量的小文件,那么会产生大量的元数据,此时会导致占用较多的内存,同时导致元数据的读写效率降低

    2. 计算:在MapReduce中,每一个小文件对应一个切片,每一个切片对应一个MapTask。如果需要对大量的小文件进行处理,那么就意味着需要产生大量的MapTask,导致集群的资源被同时大量的占用和释放

  4. 目前对小文件的处理方案无非两种:合并(merge)和打包

  5. MapReduce提供了一种原生的打包方案:Hadoop Archive,将多个小文件打成一个.har

    复制代码
    # 将/txt/打成txt.har包,放到/result下
    hadoop archive -archiveName txt.har -p /txt /result

数据倾斜

  1. 在集群中,因为处理的数据量不均等导致任务执行时间不一致而产生的等待,称之为数据倾斜

  2. 数据倾斜可能发生在Map端,也可能会发生在Reduce端

    1. Map端产生数据倾斜的直接原因:需要同时处理多个文件,且文件大小不均等,文件不可切

    2. Reduce产生数据倾斜的直接原因:对数据进行分类

  3. 数据倾斜无法避免,因为数据本身就有倾斜特性

  4. 理论上来说,数据倾斜产生之后可以解决,但是实际过程中,不太好解决;对于Reduce端的数据倾斜,实际过程中,可能会采用二阶段聚合方式来处理

推测执行机制

  1. 推测执行机制本质是MapReduce针对慢任务的一种优化。当出现慢任务的时候,MapReduce会将这个任务拷贝一份到其他节点上,两个节点同时执行相同的任务,谁先执行完谁的结果就作为最终结果,另一个没有执行完的就会被kill掉

  2. 慢任务的出现场景

    1. 任务分配不均匀:每一个节点被分配到的任务数量不同

    2. 节点性能不一致:每一台服务器的配置不同

    3. 数据倾斜:任务之间处理的数据量不均等

  3. 实际工作中,因为数据倾斜而导致的慢任务出现的概率更高,此时推测执行机制除了会占用更多的集群资源以外,并不能提高执行效率。因此,实际过程中,一般会考虑关闭推测执行机制

    复制代码
    <!-- MapTask的推测执行机制 -->
    <property>
        <name>mapreduce.map.speculative</name>
        <value>true</value>
    </property>
    <!-- ReduceTask的推测执行机制 -->
    <property>
        <name>mapreduce.reduce.speculative</name>
        <value>true</value>
    </property>

join

  1. 案例:统计每一天的利润(文件:order.txt和product.txt)

  2. 需要同时处理多个文件,且信息来源不是只依靠单个文件就能处理,此时需要确定一个主文件,将其他的文件作为缓存文件进行处理,这个过程称之为join

相关推荐
码智社14 分钟前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海17 分钟前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖1 小时前
JDK 26 新特性详解
java·开发语言
马优晨2 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区4 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大4 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai4 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜5 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa5 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio
一次旅行5 小时前
Python+大模型端到端自动化日报系统
开发语言·python·自动化