Hadoop学习笔记[7]-MapReduce运行源码分析

Hadoop学习笔记[7]-MapReduce运行源码分析

  分布式计算框架的过人之处在于可以让我们像写单机程序一样写分布式计算程序,只需要关注业务逻辑即可,剩下的事情,框架会帮我们做,前面文章也说了,虽然MR是个感觉有点“过时”的产品,但是其思想和结构相对简单,很容易理解,且是个划时代的产品,所以我们没理由看不起它

  本文将介绍MR运行流程中的核心源码,因为对资源管理这部分不熟,so,跳过yarn😂

1、MR客户端提交源码分析

  客户端最重要的工作就是根据需要处理的文件名称,从HDFS获取文件的元数据,根据文件的元数据和切片大小生成切片清单,并将其和程序jar包、配置文件一起上传到HDFS

  客户端代码的整体运行流程图如下:
在这里插入图片描述

  提交任务最终调用的是Job.submit(),该函数执行流程为:
在这里插入图片描述

  提交作业最终调用的是JobSubmitter.submitJobInternal(),该函数执行流程为:
在这里插入图片描述

  重点来了,生成切片清单并提交到HDFS,调用的是JobSubmitter.writeSplits(),程序流程为:
在这里插入图片描述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HWgMeazS-1644412068806)(./image/MR/image-20220105184244285.png)]

  最终调用的是FileInputFormat的getSplits方法生成切片清单,程序流程为:
在这里插入图片描述

  客户端流程大概就这些,在程序中,计算切片大小的公式是:
Max(切片参数最小值,Min(切片参数最大值,块大小))
,假设不改配置参数,则计算公式就是Max(1,Min(Long最大值,块大小))=块大小,
根据这个公式可以得出推论:如果要使切片大小变大,则需要调整最小值参数,切片变小则要调整最大值参数

2、Map任务源码分析

  Map任务应该算是整个MR程序中比较复杂的部分了,Map任务和Reduce任务最终都会由YarnChild调用,其入口方法是Task类的run方法,具体实现类是MapTask和ReduceTask

2-1 主流程

  主流程的入口是MapTask.run(),整体流程如下:
在这里插入图片描述

  通过调用runNewMapper()执行实际的Map任务,程序流程为:
在这里插入图片描述

这里需要思考一个问题:HDFS在分割文件成block的时候是线性按照字节切割,例如wordCount中,假设一个split对应一个block,对于单词hello,可能前一个切片存了he,后一个切片存了llo,那么MR如何避免统计错误?

  思路很巧妙,以TextInputFormat为例(最终都是由LineRecorReader类实现具体的功能),除了处理第一个切片的map任务,其余Map任务在处理数据时会过滤掉自己负责切片的第一行,也就是不读取第一行,无论第一行是否完整,第二行肯定是都没问题的,既然自己不读取第一行,那么这一行就得由处理上一个切片的Map任务读取,实现也很容易,虽然在逻辑上该Map任务处理的是一个切片,但是Map任务都是调用HDFS的客户端读文件,对于Map任务而言,只能看到文件,感知不到切片的存在,所以上一个任务在读取到最后一行时,如果最后一行不完整,那么肯定没有换行符,换行符肯定在下一个切片的第一行,所以直接读取到换行符未知即可,即使读取到换行符,意味着已经到切片尾部,控制程序再往后读取一行也不是什么问题

  在这个函数里会通过调用Mapper类的run方法,间接调用我们实现业务逻辑的Mapper类(也就是我们自己写的带有map方法的类),程序流程为:
在这里插入图片描述

一般来说,MR程序的Map任务都是继承Mapper类,重写map方法,map方法最终会被Mapper类的run方法调用,调用流程就是图中的最后一个部分,每一行调用一次map方法,循环结束条件是是否有下一行返回否,那么什么时候返回否?

  在构造split的时候肯定得知道split的起始位置和结束位置,所以当LineRecordReader在读取某一行数据的时候,当该行记录的起始偏移量大于split的结束偏移量时就结束,为什么是大于???回看一下上面的问题,无论如何,我们都要多读取一行,如果本切片的最后一个字符刚好是换行符,那么此时两个偏移量就是相等的,只有此时判断为没有结束,才能接着向下读一行,读出的数据需要构造KV对象,Key在这里其实没啥用,默认设置的就是读取的起始位置,value是本次获取的一行记录

在MR中如果没有reduce任务,那么会直接通过LineRecordWriter对象将数据写回HDFS,如果存在Reduce任务时,会创建数据输出缓冲层,MapTask的输出由内部类NewOutputCollector负责,这部分比较复杂,下面分别介绍

2-2 NewOutputCollector详细分析

这个类一般也不会自己实现,主要是真的太复杂了,整个类的实现主要分为4个部分:1、初始化;2、写数据;3、数据溢写;4、合并文件

2-2-1 初始化

  主流程的入口是New NewOutputCollector(),整体流程如下:
在这里插入图片描述

  主要工作就两个:实例化collector对象和分区器对象,分区器对象比较简单,所以着重介绍collector对象,其入口是createSortingCollector(),从名字上看就知道这个记录收集器带排序功能,构建流程如下:
在这里插入图片描述

  图中重要的步骤就是初始化记录收集器,实现类是MapOutPutBuffer,入口函数MapOutputBuffer.init(),流程如下:
在这里插入图片描述

  简而言之,这是个环形的buffer,记录都是先写到这个buffer,再输出到文件,这里面有个Combiner对象需要介绍一下,Combiner其实就是一个Map端的reduce任务,系统没有默认的实现,所以如果没有配置实现类就是空,可以用简单的例子说明一下它的作用:假设现在在进行WC的计算任务,原始输出是[{K1 1},{K1 1},{K1 1}]这种KV键值对,K1是单词,value默认都是1,其实可以在Map端先将K1汇总一下,变成K1 3,针对这个K1,数据量就变成1/3,可以减少后级的网络IO,Combiner会在两个位置触发:1)溢写文件时触发;2)在最终将小文件合并成大文件时,如果小文件个数比设定的阈值大,也会在合并中调用combiner

2-2-2 写数据

  写数据的整体流程如下:
在这里插入图片描述

  最终是调用MapOutputbuffer.collect()方法将数据写入缓冲区,图解一下这个流程,开始

在介绍写入流程之前,先介绍一下几个基本的数据结构,首先先明确一下,在分布式系统,数据要在多节点之间进行网络传输,所以最终都需要转换成字节存储,假设我们有一个字节数组,同时需要向其存入两个KV对象【KV对象需要序列化成Byte数组】,假设我们直接就往buffer里面写,会存成如下的形式
在这里插入图片描述

可以看出来两个KV对象长度不一样,现在如果想恢复出两个KV对象,其实是没有办法的,因为不知道K1的起始位置和结束位置在哪【肉眼可见是因为我们标了颜色区分,实际计算机都是二进制数字,都一样】,那么该怎么办?我们至少得有个地方存储K的起始位置、结束位置、V的起始位置和结束位置,由于KV都是连续存储,知道其中一个,起始就可以知道另外一个的某个边界,比如知道V的起始位置,将其-1就是K的结束位置,所以只需要3个变量K起始、V起始、V结束即可定位一个KV对象,在实际存储中我们会先序列化出KV,所以V结束=V起始+V长度,所以我们只需要有个地方存一下K起始、V起始、V长度就能恢复出这个KV对象

Hadoop也基本遵循这个思路,在程序里这部分数据就叫Kvmeta【KV元数据】,Kvmeta的长度固定,每一个KV对象的元数据长度都是16Byte【4个Int类型值,不是说好的是3个变量就行,怎么会是4个,回忆一下之前说的,最终输出文件要按照分区排序,所以需要有个位置存储记录的分区,多出来的4byte存的就是分区】,其结构如下
在这里插入图片描述

为了减少空间的浪费,hadoop将这个Buffer分为两个区域,数据区和元数据区,数据区从头部开始往后写,元数据区从尾部开始往前写,所以经过设计后,这两个KV对象在buffer中的存储位置如下
在这里插入图片描述

由于元数据的长度固定,假设现在需要恢复K2V2,只要先定位到K2的元数据,在根据元数据的信息即可恢复出该KV对象

其实还有个问题,假设现在已经到达80%的阈值,开始溢写文件,在溢写结束之前,这部分空间会被锁住,新来的数据只能向剩余的20%写入,这会会有个问题,从哪个位置开始往后写数据,从哪个位置开始往前写元数据,如果是直接接在原来的位置数据和元数据后面,肯定会造成数据和元数据会在某一个时刻相遇,这会往前往后都不行,只能溢写,违背了80%溢写的规则,为了解决这个问题,hadoop将缓冲区设计成了环形缓冲区,初始状态缓冲区如下
在这里插入图片描述

假设现在已经到达80%的阈值,KV数据和KV元数据所消耗的存储空间会被锁住,开始溢写文件,同时程序会移动内存边界,也就是在剩余的20%空间中定一个内存边界【赤道】从逻辑上看,这个存储buffer是个环,赤道在哪都行,然后就可以和开始一样,往赤道的一边写数据,另一边写元数据,假设溢写比例调的足够好,在20%空间用完之前,已经溢写完成,则之前的80%空间就会被释放,,不会出现内存重叠的问题,再画个图表示一下
在这里插入图片描述

2-2-3 溢写文件

  溢写文件最终都会交给专门的溢写线程负责,最终调用的方法时sortAndspill(),从方法的名字就可以看出溢写的时候会排序,为什么排序在开始介绍MR的时候已经说过,主要就是减少reduce的IO,整体流程如下
在这里插入图片描述

2-2-4 合并文件

  合并文件发生在Map任务完成后,会调用output对象的flush方法,最终会调用MapOutputBuffer的flush方法,整体流程如下
在这里插入图片描述

3、Reduce任务源码分析

Reduce任务主要有三个阶段

  • 1)、从各Map任务的输出拉取回自己负责的数据【根据分区号】
  • 2)、对文件进行一次归并,将不同Map任务的输出归并成一个文件
  • 3)、运行reduce任务,这里强调一下一个概念,Map任务是一条记录调用一次map方法,Reduce任务是相同的key为一组,一组调用一次reduce方法,关键要解决的是这个一组是怎么来的?

Reduce任务的整体流程如下
在这里插入图片描述

图中的分组排序器是实现记录分组的关键,分组排序器如果用户没配置,默认取的就是Map输出Key的类型中实现的排序器,正常的排序有三种状态,大于,等于和小于,但是判断是否相同的一组只有两个状态,所以使用这个排序器还需要单独处理一下排序器的返回结果,只有返回0时才代表是同一组

最终调用调用runNewReducer()执行实际的reduce任务,该方法的流程图如下:
在这里插入图片描述

和Map任务一样,最后也是通过调用Reduce类的run方法,间接调用配置的Reducer类,调用的是Reducer.run(),该方法流程如下:
在这里插入图片描述

图中实现一组调用一次reduce方法的关键就是循环的判断条件上,该判断条件会调用ReduceContextImpl.nextKey()方法,这个方法内部有两个重要变量:hasMorenextKeyIsSame,都是布尔变量,代表的含义是:是否还有数据没处理完和下一条KV对象的Key和自己是否相同,如果相同,就说明是同一组,由于在reduce端处理的文件都是按照key排序,所以如果相邻两个KV对象的Key不同,则说明上一个Key对应的所有输出都已经处理完,可以处理下一个Key,这就是相同Key为一组的概念,该方法流程如下:
在这里插入图片描述

图中调用nextKeyValue方法过滤掉这一组Key剩余的记录的作用是如果reduce方法不需要处理这一组的所有数据就退出,那么框架要跳过这一组的剩余元素,直接取下一组数据,最终调用的都是ReduceContextImpl.nextKeyValue()读取数据,程序流程为:
在这里插入图片描述

以上就是Reduce方法的整体执行流程

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
hadoop-mapreduce-client-core是Hadoop分布式计算框架中的核心模块之一。它主要包含了Hadoop MapReduce的核心功能和API接口,是实现MapReduce编程模型的必备组件。 Hadoop MapReduce是一种用于大规模数据处理的编程模型,其核心思想是将大规模数据集分解成多个较小的数据块,分别在集群中的不同机器上进行处理,最后将结果整合。hadoop-mapreduce-client-core模块提供了与MapReduce相关的类和方法,方便开发者实现自定义的Map和Reduce任务。 具体来说,hadoop-mapreduce-client-core模块包含了以下重要组件和功能: 1. Job:Job表示一个MapReduce任务的定义和描述,包括输入路径、输出路径、Mapper和Reducer等。 2. Mapper:Mapper是MapReduce任务中的映射函数,它负责将输入数据转换成<key, value>键值对的形式。 3. Reducer:Reducer是MapReduce任务中的归约函数,它按照相同的key将所有Mapper输出的value进行聚合处理。 4. InputFormat:InputFormat负责将输入数据切分成多个InputSplit,每个InputSplit由一个Mapper负责处理。 5. OutputFormat:OutputFormat负责将Reducer的输出结果写入指定的输出路径中。 使用hadoop-mapreduce-client-core模块,开发者可以基于Hadoop分布式计算框架快速开发并行处理大规模数据的应用程序。通过编写自定义的Mapper和Reducer,可以实现各种类型的分布式计算,如数据清洗、聚合分析、机器学习等。 总之,hadoop-mapreduce-client-core是Hadoop分布式计算框架中的核心模块,提供了实现MapReduce编程模型所需的基本功能和API接口。使用该模块,开发者可以利用Hadoop的分布式计算能力,高效地处理和分析大规模数据。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值