【Hadoop】MapReduce入门解析

MapReduce

MapReduce简介及优点

  • MapReduce是一个分布式运算程序的编程框架,是Hadoop数据分析的核心
  • MapReduce的核心思想是将用户编写的逻辑代码和架构中的各个组件整合成一个分布式运算程序,实现一定程序的并行处理海量数据,提高效率
  • 海量数据难以在单机上处理,而一旦将单机版程序扩展到集群上进行分布式运行势必将大大增加程序的复杂程度,所以引入MapReduce架构,开发人员可以将精力集中于数据处理的核心业务逻辑上,而将分布式程序中的公共功能封装成框架,以降低开发的难度
  • 一个完整的MapReduce 程序有三类实例进程
    • MRAppMaster:负责整个程序的协调过程
    • MapTask:负责map阶段的数据处理
    • ReduceTask:负责reduce阶段的数据处理

MapReduce基本流程

第一步:InputFormat
第二步:Split
第三步:RecordReader
第四步:Map
第五步:Partition
第七步:Sort (Sort之前会进行Combiner)
第七步:Merge (Merge之前会进行Combiner)
第八步:Group
第九步:Reduce
第十步:OutputFormat
在这里插入图片描述

MapReduce整体详细流程

1.Map阶段

1、数据读取、逻辑切片
   首先,读取数据的组件InputFormat(默认TextInputFormat)
   会通过getSplits方法对输入目录中的文件进行逻辑切片规划得到splits切片
   有多少个split就对应启动多少个MapTask,默认情况下split与block的对应关系默认是一对一

2、RecordReader处理输出
   将输入文件切分为splits之后,由RecordReader对象(默认LineRecordReader)进行读取
   以\n作为分隔符,读取一行数据,返回<key,value>
   Key表示每行首字符偏移值,value表示这一行文本内容
 
3、执行map方法
   读取split返回<key,value>,进入用户自己继承的Mapper类中,执行用户重写的map函数
   RecordReader读取一行,用户重写的map调用一次,并输出一个<key,value>

2.Shuffle阶段(Map输出之后,Reduce输出之前)

请添加图片描述
请添加图片描述

1.数据分区、写入内存
  此时outputCollectot,会收集Map写出的数据,将key/value以及Partition的结果将并放入内存中的环形缓冲区
  
  注意:
  a.在写到环形缓冲区的过程中进行分区
  b.缓冲区的作用是批量收集map结果,减少磁盘IO的影响
  c.使用环形数据结构是为了更有效地使用内存空间,在内存中放置尽可能多的数据;
  d.环形缓冲区其实是一个字节数组,数组中存放着key、value的序列化数据和key、value的元数据信息,
  包括partition、key的起始位置、value的起始位置以及value的长度;环形结构是一个抽象概念

  e.缓冲区是有大小限制,默认是100MB,可修改;当Map task的输出结果很多时,就可能会撑爆内存,
    所以需要在一定条件下将缓冲区中的数据临时写入磁盘,然后重新利用这块缓冲区;
    这个从内存往磁盘写数据的过程被称为Spill,中文可译为溢写;这个溢写是由单独线程来完成,
    不影响往缓冲区写map结果的线程;溢写触发的比例默认是0.8,也就是当缓冲区的数据已经达到阈值时,
    溢写线程启动,锁定这80MB的内存,执行溢写过程。Map task的输出结果还可以往剩下的20MB内存中写,互不影响

2、溢写、排序、combine(局部合并)
   当环形缓冲区中的数据到达一定比例时,开启溢写( 从内存往磁盘写数据的过程被称为Spill,中文可译为溢写 ),
   锁定需要溢写的数据,将需要溢写的数据写到磁盘中的临时文件中,
   并在写入前根据key进行 排序(sort)和合并(combine,可选操作)
   
   注意:
   		a.分区(Partitioner)
			MapReduce提供Partitioner接口,它的作用就是根据key或value及reduce的数量
			来决定当前的这对输出数据最终应该交由哪个Reducetask处理;
			默认对key HashPartitioner 后再以 Reducetask 数量取模;
			默认的取模方式只是为了平均Reduce的处理能力,如果用户自己对Partitioner有需求,可自行设置;

		b.排序(key.compareTo):调用key的compareTo方法进行比较,默认是快排,字典排序
		
	    c.Combiner:作用是进行局部汇总;工作机制与Reduce完全一致;由用户决定是否使用
			
		d.数据区会有三个数据来记录索引:顺序,大小,偏移量等

		e.当80%的数据区被写满之后,数据区会被锁定,此时outputcollector收集的数据会写入20%的保留区。
		  当数据区的数据溢写完成后,锁定解除。

		f.此时的20%的保留区加上数据区的60%构成新的数据区,数据区剩余的20%为新的保留区

		g.保留区与数据区的比例是可以由用户决定的。
		
		h.生成的每个文件叫做段(segment),每个文件还会有一个index索引文件(用于描述区的偏移量,
		原始文件大小,压缩后数据大小),该文件默认存储在内存,内存空间不足够时溢写到磁盘

3.merge(整合)
    当整个数据处理结束之后,会对磁盘中所有的临时文件进行归并,生成一个大文件
	此时的归并是将所有临时文件中的相同分区的合并到一起,并对各个分区中的数据再进行一次排序(sort)


	需要注意的问题:
		a.一个Maptask最后会整合成一个大文件,大文件也会有索引
		b.merge的过程中combiner也会起作用
		c.reducetask会去每个Maptask上拉取对应分区的文件,默认存放在reduce端的环形缓冲区中(100M),
		达到阈值溢写磁盘,拉取完毕后,会得到一堆小文件,对所有的小文件,进行merge,归并排序成一个大文件

在这里插入图片描述

3.Reduce阶段

1.拉取、分组、合并数据
  ReduceTask不断从MapTask拉取属于自己分区的数据读取到内存中的缓存区里,到达阈值后溢写到磁盘中,
  内存中的所有数据写入完成之后开始做merge归并,最后合并成一个分区相同的大文件,然后对这个
  文件中的键值对按照key进行sort排序,排好序之后根据分区规则进行分组,分组完成后一组一组的调用reduce方法
2.执行reduce方法

在这里插入图片描述

MapReduce 调优

  • Map端的最高效率是:尽量减少环形缓冲区flush的次数(减少磁盘IO 的使用次数)
    如何能够减少环形缓冲区flush的次数:

    • 1、加大环形缓冲区的内存
    • 2、增大缓冲区阈值的大小 (考虑剩余的空间是不是够系统使用)
    • 3、对输出的进行压缩(压缩-解压的过程会消耗CPU)
  • Reduce端的最高效率是:尽量减少环形缓冲区flush的次数

    • 1、尽量将所有的数据在内存中计算
  • 在网络带宽、磁盘IO是瓶颈的前提下,能不使用IO 网络就不使用,在必须使用的前提下,能少用就少用。

  • 所有的、只要能够减少网络带宽的开销,只要能够减少磁盘io的使用的次数的配置项,都是集群调优的可选项;(可选项包括: 软件层面【系统软件和集群软件】,硬件层面,网络层面)

MapReduce开发总结

mapreduce在编程的时候,基本上一个固化的模式,没有太多可灵活改变的地方,除了以下几处:

1)输入数据接口:
   InputFormat--->FileInputFormat(文件类型数据读取的通用抽象类)  DBInputFormat (数据库数据读取的通用抽象类)
   默认使用的实现类是:TextInputFormat 
   job.setInputFormatClass(TextInputFormat.class)
   TextInputFormat的功能逻辑是:一次读一行文本,然后将该行的起始偏移量作为key,行内容作为value返回
2)逻辑处理接口: Mapper  
   完全需要用户自己去实现其中:map()   setup()   clean() 
3)map输出的结果在shuffle阶段会被partition以及sort,此处有两个接口可自定义:
    (1)Partitioner
	有默认实现 HashPartitioner,逻辑是  根据key和numReduces来返回一个分区号; 
	key.hashCode()&Integer.MAXVALUE % numReduces
	通常情况下,用默认的这个HashPartitioner就可以,如果业务上有特别的需求,可以自定义
	(2)Comparable
	当我们用自定义的对象作为key来输出时,就必须要实现WritableComparable接口,override其中的compareTo()方法
4)reduce端的数据分组比较接口:Groupingcomparator
	reduceTask拿到输入数据(一个partition的所有数据)后,首先需要对数据进行分组,其分组的默认原则是key相同,
	然后对每一组kv数据调用一次reduce()方法,并且将这一组kv中的第一个kv的key作为参数传给reduce的key,
	将这一组数据的value的迭代器传给reduce()的values参数
	利用上述这个机制,我们可以实现一个高效的分组取最大值的逻辑:
	自定义一个bean对象用来封装我们的数据,然后改写其compareTo方法产生倒序排序的效果
	然后自定义一个Groupingcomparator,将bean对象的分组逻辑改成按照我们的业务分组id来分组(比如订单号)
	这样,我们要取的最大值就是reduce()方法中传进来key
5)逻辑处理接口:Reducer
	完全需要用户自己去实现其中  reduce()   setup()   clean()   
6)输出数据接口:OutputFormat---> 有一系列子类FileOutputformat  DBoutputFormat  .....
	默认实现类是TextOutputFormat,功能逻辑是:将每一个KV对向目标文本文件中输出为一行
  • 10
    点赞
  • 8
    收藏
  • 打赏
    打赏
  • 8
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:酷酷鲨 设计师:CSDN官方博客 返回首页
评论 8

打赏作者

默默走开

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值