Hadoop学习笔记:五、MapReduce

目标:

  • 了解MapReduce的优缺点
  • 重点:掌握MapReduce的核心思想
  • 掌握MapReduce的框架原理
  • 重点:掌握Shuffle机制
  • 理解Combiner作用
  • 理解Join的作用
  • 掌握Hadoop的数据压缩

1 MapReduce概述

1.1 MapReduce定义

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

1.2 MapReduce优缺点
  • 优点
    • MapReduce易于编程:简单的实现一些接口,就可以完成一个分布式程序
    • 良好的扩展性:当计算资源不足时,可以通过增加机器来扩展
    • 高容错性:其中一台机器挂了,可以把上面的计算任务转移到另一个节点上运行,不至于这个任务运行失败
    • 适合PB级以上海量数据的离线处理
  • 缺点
    • 不擅长实时计算:无法在毫秒或者秒级内返回结果
    • 不擅长流式计算:MapReduce的输入数据是静态的
    • 不擅长DAG(有向无环图)计算:每个MapReduce的作业输出结果都会写入到磁盘,会造成大量的磁盘IO,导致性能非常的低下
1.3 MapReduce核心思想

image-20210311132641812

  • 核心思想解析如下:
    • 分布式的运算程序需要至少2个阶段,那么第一个阶段的MapTask并发实例,完全运行,互不相干
    • 第二个阶段的ReduceTask并发实例互不相干,但是它们的数据 依赖于上一个阶段的所有MapTask并发实例的输出
    • MapReduce编程模型只能包含一个Map阶段和一个Reduce阶段,如果用户的业务逻辑非常复杂,那就只能多个MapReduce程序,串行执行。
  • 一个完整的MapReduce程序在分布式运行时有三类实例进程
    • MrAppMaster:负责整个程序的过程调度及状态协调
    • MapTask:负责Map阶段的整个数据处理流程
    • ReduceTask:负责Reduce阶段的整个数据处理流程
1.4 MapReduce编程规范

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

  • Mapper阶段

    • 用户自定义的Mapper要继承自己的父类
    • Mapper的输入数据是KV对的形式(可自定义)
    • Mapper的业务逻辑写在map()方法中
    • Mapper的输出数据是KV对的形式(可自定义)
    • map()方法(MapTask进程)对每一个<K,V>调用一次
  • Reducer阶段

    • 用户自定义的Reducer要继承自己的父类
    • Reducer的输入数据类型对应Mapper的输出数据类型,也是KV
    • Reducer的业务逻辑卸载reduce()方法中
    • ReduceTask进程对每一组相同的<K,V>组调用一次reduce()方法
  • Driver阶段

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

  • 示例如下:WordCount

    image-20210311171358857

2 Hadoop序列化

2.1 序列化概述
  • 定义

    • 序列化:把内存中的对象,转换成字节序列(或其他数据传输协议)以便于存储到磁盘(持久化)和网络传输
    • 反序列化:将收到的字节序列(或其他数据传输协议)或者是磁盘的持久化数据,转换成内存中的对象。
  • 为什么要进行序列化

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

    • 紧凑:高效使用存储空间
    • 快速:读写数据的额外开销小
    • 可扩展:随着通信协议的升级而升级
    • 互操作:支持多语言的交互
  • 自定义bean对象实现序列化接口(Writable)

    • 在企业开发中往往常用的基本序列化类型不能满足所有需求,比如在Hadoop框架内部传递一个bean对象,那么该对象就需要实现序列化接口

3 MapReduce框架原理

3.1 InputFormat数据输入
  • MapTask并行度决定机制

    image-20210311180937819

    • 数据块:block是HDFS物理上把数据分成一块一块
    • 数据切片:数据切片只是逻辑上对输入进行分片,并不会在磁盘上将其切分成片进行存储
  • Job提交流程源码解析示意图

    image-20210311181051058

3.2 FileInputFormat切片机制
  • 切片机制
    • 简单的按照文件的内容长度进行切片
    • 切片大小,默认等于block大小
    • 切片时不考虑数据集整体,而是逐个针对每一个文件单独切片
3.3 CombineTextInputFormat切片机制

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

  • 应用场景:CombineTextInputFormat用于小文件过多的场景,可以将多个小文件从逻辑上规划到一个切片中,这样多个小文件就可以交给一个MapTask处理
  • 虚拟存储切片最大值设置:最好根据实际的小文件大小情况来设置具体的值
  • 切片机制:包括虚拟存储过程和切片过程
3.4 自定义InputFormat
  • 自定义InputFormat步骤如下:
    • 自定义一个类继承FileInputFormat
    • 改写RecordReader,实现一次读取一个完整文件封装成KV
    • 在输出时使用SequenceFileOutPutFormat输出合并文件

4 MapReduce工作流程

MapReduce是Hadoop的核心,而Shuffle是MapReduce的核心。

4.1 流程示意图
  • 详细工作流程一

image-20210311204029675

  • 详细工作流程二

    image-20210311204111503

  • 流程详解

    上面的两个流程是整个MapReduce的最全工作流程。其中Shuffle过程是从第7步开始到第16步结束。具体的Shuffle过程如下:

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

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

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

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

    • ReduceTask 根据自己的分区号,去各个 MapTask 机器上取相应的结果分区数据

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

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

  • 注意:

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

Map方法之后,Reduce方法之前的数据处理过程称之为Shuffle。

image-20210311212930660

4.3 Partition分区

默认分区是根据key的hashCode对ReduceTasks个数取模得到的,用户没法控制哪个key存储到哪个分区。

  • 分区总结:
    • 如果ReduceTask数量>getPartition的结果数,则会多产生几个空的输出文件part-r-000xx
    • 如果1<ReduceTask的数量<getPartition的结果数,则有一部分分区数据无处安放,会Exception
    • 如果ReduceTask的数量=1,则不管MapTask端输出多少个分区文件,最终结果都交给这一个ReduceTask,最终也就只会产生一个结果文件 part-r-00000
    • 分区号必须从0开始逐一累加
  • 自定义Partitioner
    • 自定义继承Partitioner,重写getPartition()方法
    • 在job驱动中,设置自定义Partitioner
    • 自定义Partiton后,要根据自定义Partitioner的逻辑设置相应数量的ReduceTask
4.4 WritableComparable排序

排序是MapReduce框架的最重要的操作之一。MapTask和ReduceTask均会对数据按照key进行排序。任何应用程序的数据均会被排序,而不管逻辑上是否需要。

默认排序是按照字典顺序排序,且实现该排序的方法是快速排序。

  • 排序概述

    • 对于MapTask,它会将处理的结果暂时放到环形缓冲区内,当环形缓冲区使用率达到一定阈值之后,再对缓冲区中的数据进行一次快速排序,并将这些有序数据溢写到磁盘上,而当数据处理完成之后,会对磁盘上所有文件进行归并排序。
    • 对于ReduceTask,它从每个MapTask上远程拷贝相应的数据文件,如果文件大小超过一定阈值,则溢写磁盘上,否则存储在内存中。如果磁盘上文件数目达到一定阈值,则进行一次归并排序以生成一个更大文件;如果内存中文件大小或者数目超过一定阈值,则进行一次合并后将数据溢写到磁盘上。当所有数据拷贝完毕后,ReduceTask统一对内存和磁盘上的所有数据进行一次归并排序。
  • 排序分类

    • 内部排序:MapReduce根据输入记录的键对数据集排序,保证输出的每个文件内部是有序的
    • 全排序:最终的输出结果只有一个文件,且文件内部是有序的。实现方式是只设置一个ReduceTask。但该方法处理大型文件时效率极低。
    • 辅助排序(GroupingComparator分组):在Reduce端对key进行分组。应用于:在接收的key为bean对象时,想让一个或几个字段相同的key进入到一个reduce方法时,可以采用分组排序。
    • 二次排序:在自定义的排序中,如果compareTo中的判断条件为两个即为二次排序
  • 自定义排序WritableComparable

    • 原理:bean对象为key传输,需要实现WritableComparable接口重写compareTo方法。
4.5 Combiner合并
  • 概述
    • Combiner是MR程序中Mapper和Reducer之外的一种组件,其父类就是Reducer
    • Combiner和Reducer的区别在于:Combiner是在每个MapTask所在的节点运行,而Reducer是接收全局所有Mapper的输出结果
    • Combiner的意义就是对每一个MapTask的输出进行局部汇总,以减少网络传输量,解决网络IO问题
    • Combiner能够应用的前提是不能影响最终的业务逻辑,而且,Combiner的输出kv应该跟Reducer的输入kv类型要对应起来
  • 自定义Combiner实现步骤
    • 自定义一个Combiner继承Reducer,重写Reduce方法

5 MapTask与ReduceTask工作机制

5.11 MapTask工作机制

image-20210312114846055

  • MapTask工作机制解析

    • Read阶段:MapTask通过用户编写的RecordReader,从输入InputSplit中解析出一个个key/value

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

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

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

      • 溢写阶段详情

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

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

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

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

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

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

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

5.2 ReduceTask工作机制

image-20210312130052349

  • ReduceTask工作机制解析
    • Copy阶段:ReduceTask 从各个 MapTask 上远程拷贝一片数据,并针对某一片数据,如果其大小超过一定阈值,则写到磁盘上,否则直接放到内存中。
    • Merge阶段:在远程拷贝数据的同时,ReduceTask 启动了两个后台线程对内存和磁盘上的文件进行合并,以防止内存使用过多或磁盘上文件过多。
    • Sort阶段:按照 MapReduce 语义,用户编写 reduce()函数输入数据是按 key 进行聚集的一组数据。为了将 key 相同的数据聚在一起,Hadoop 采用了基于排序的策略。由于各个 MapTask 已经实现对自己的处理结果进行了局部排序,因此,ReduceTask 只需对所有数据进行一次归并排序即可
    • Reduce阶段:reduce()函数将计算结果写到HDFS上
  • 设置ReduceTask并行度
    • ReduceTask的并行度同样影响整个job的执行并发度和执行效率,但与MapTask的并发数由切片数决定不同,ReduceTask数量的决定是可以通过直接手动设置。
  • 注意
    • ReduceTask=0,表示没有Reduce阶段,输出文件个数和Map个数一致
    • ReduceTask的默认值就是1,所以就输出一个文件
    • 如果数据分步不均匀,就有可能在Reduce阶段产生数据倾斜
    • ReduceTask数量并不是任意设置,还要考虑业务逻辑要求,有些情况下,需要计算全局汇总结果,就只能有1个ReduceTask。具体多少个ReduceTask要根据集群性能而定
    • 如果分区数不是1,但是ReduceTask为1,是否执行分区过程。答案是:不执行分区过程。因为在MapTask的源码中,执行分区的前提是先判断ReduceNum个数是否大于1。不大于1肯定不执行

6 OutputFormat数据输出

6.1 OutputFormat接口实现类

OutputFormat是MapReduce输出的基类,所有实现MapReduce输出都实现了OutputFormat接口。

  • 文本输出TextOutputFormat:默认输出格式为TextOutputFormat,它把没跳记录写为文本行
  • SequenceFileOutputFormat:将SequenceFileOutputFormat输出作为后续MapReduce任务的输入,其格式紧凑,容易被压缩
  • 自定义OutputFormat:为了实现控制最终文件的输出路径和输出格式,可以自定义OutputFormat。
    • 例如:要在一个MapReduce程序中根据数据的不同输出两类结果到不同目录,这类灵活的输出需求可以通过自定义OutputFormat来实现。
  • 自定义OutputFormat步骤:
    • 自定义一个类继承FileOutputFormat
    • 改写RecordWriter,具体改写输出数据的方法write()

7 MapReduce开发总结

7.1 编写MapReduce程序时,需要考虑如下几个方面:
  • 输入数据接口:InputFormat
    • 默认用的实现类是:TextInputFormat
    • TextInputFormat的功能逻辑是:一次读一行文本,然后将改行的起始偏移量作为key,行内容作为value返回
    • KeyValueTextInputFormat每一行均为一行记录,被分隔符分割为key,value。默认分隔符是tab(\t)
    • NlineInputFormat按照指定的行数N来划分切片
    • CombineTextInputFormat可以把多个小文件合并成一个切片处理,提高处理效率
    • 用户还可以自定义InputFormat
  • 逻辑处理接口:Mapper
    • 用户根据业务需求实现其中三个方法:map()、setup()、cleanup()
  • Partitoner分区
    • 有默认实现 HashPartitioner , 逻辑是根据 key的哈希值和numReduces来返回一个分区号
    • 可以自定义分区
  • Compareable排序
    • 当我们用自定义的对象作为key来输出时,就必须要实现WritableComparable接口,重写其中的compareTo()方法
    • 部分排序:对最终输出的每一个文件进行内部排序
    • 全排序:对所有数据进行排序,通常只有一个Reduce
    • 二次排序:排序的条件有两个
  • Combiner合并
    • Combiner合并可以提高程序执行效率,减少IO传输。但是使用时必须不能影响原有的业务处理结果。
  • Reduce端分组:GroupingComparator
    • 在Reduce端对key进行分组。应用于:在接收的key为bean对象时,想让一个或几个字段相同(全部字段比较不相同)的key进入到同一个reduce方法时,可以采用分组排序
  • 逻辑处理接口:Reducer
    • 用户实现其中的三个方法:reduce()、setup()、cleanup()
  • 数据输出接口:OutputFormat
    • 默认实现类是:TextOutputFormat,功能逻辑是:将每一个kv对,向目标文件输出一行
    • 将SequenceFileOutputFormat输出作为后续 MapReduce任务的输入,这便是一种好的输出格式,因为它的格式紧凑,很容易被压缩
    • 用户自定义OutputFormat

小结

本文主要是总结MR的理论及实践开发,继续加油吧!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值