hadoop基本概念

167 阅读29分钟

一、hadoop内部架构

hadoop由3部分组成,分布式存储HDFS、分布式计算MapReduce、资源调度引擎Yarn。

1、分布式存储hdfs

介绍:

hdfs是hadoop的分布式文件系统,负责为用户创建文件,存入、读出、修改、转储、删除文件等。hdfs的架构是主从架构Master|Slave或称为管理节点|工作节点。

基本组成:

namenode:

HDFS的主节点,负责管理文件系统的命名空间,将HDFS的元数据metadate、储在NameNode节点的内存中,负责响应客户端对文件的读写请求。

  • 元数据:文件目录树、所有的文件(目录)名称、文件属性(生成时间、副本、权限)每个文件的块列表、每个block块所在的datanode列表,每个文件、目录、block占用大概150Byte字节的元数据,所以HDFS适合存储大文件,不适合存储小文件。元数据有两种形式保存,分别是edits log和fsimage。
  • edits log:HDFS编辑日志文件 ,保存客户端对HDFS的所有更改记录,如增、删、重命名文件(目录),这些操作会修改HDFS目录树;NameNode会在编辑日志edit日志中记录下来。
  • fsimage:HDFS元数据镜像文件 ,即将namenode内存中的数据落入磁盘生成的文件;保存了文件系统目录树信息以及文件、块、datanode的映射关系

datenode

存储block块和block元数据到datenode本地磁盘,此处的元数据包括数据块的长度、块数据的校验和、时间戳

  • 心跳机制:
  1. NameNode启动的时候,会开一个ipc server在那里
  2. DataNode启动后向NameNode注册,每隔3秒钟向NameNode发送一个“心跳heartbeat”
  3. 心跳返回结果带有NameNode给该DataNode的命令,如复制块数据到另一DataNode,或删除某个数据块
  4. 如果超过10分钟NameNode没有收到某个DataNode 的心跳,则认为该DataNode节点不可用
  5. DataNode周期性(6小时)的向NameNode上报当前DataNode上的块状态报告BlockReport;块状态报告包含了一个该 Datanode上所有数据块的列表
  • 作用:
  1. 通过周期心跳,NameNode可以向DataNode返回指令
  2. 可以判断DataNode是否在线
  3. 通过BlockReport,NameNode能够知道各DataNode的存储情况,如磁盘利用率、块列表;跟负载均衡有关
  4. hadoop集群刚开始启动时,99.9%的block没有达到最小副本数(dfs.namenode.replication.min默认值为1),集群处于安全模式,涉及BlockReport;

SecondaryNameNode

为什么引入SecondaryNameNode? editlog记录客户端对hdfs的所有操作记录,一旦出现故障可以从editlog恢复,但是日志大小会随着时间越来越大,恢复时间会越来越长。为了避免这种情况引入checkpoint检查点机制。namenode如果重启,可以将磁盘中的fsimage文件读入内容,将元数据恢复到某一个检查点,然后再执行检查点之后记录的编辑日志,最后完全恢复元数据。引入secondarynamenode辅助namenode,用来合并fsimage及editlog

  • SecondaryNameNode定期做checkpoint检查点操作

    • 创建检查点checkpoint的两大条件:
      • SecondaryNameNode每隔1小时创建一个检查点
      • 另外,Secondary NameNode每1分钟检查一次,从上一检查点开始,edits日志文件中是否已包括100万个事务,如果是,也会创建检查点
    • Secondary NameNode首先请求原NameNode进行edits的滚动,这样新的编辑操作就能够进入新的文件中
    • Secondary NameNode通过HTTP GET方式读取原NameNode中的fsimage及edits
    • Secondary NameNode读取fsimage到内存中,然后执行edits中的每个操作,并创建一个新的统一的fsimage文件
    • Secondary NameNode通过HTTP PUT方式将新的fsimage发送到原NameNode
    • 原NameNode用新的fsimage替换旧的fsimage,同时系统会更新fsimage文件到记录检查点的时间。
    • 这个过程结束后,NameNode就有了最新的fsimage文件和更小的edits文件

block

向hdfs上传文件的时候,是以128m为单位,切分成一个个block,分散存储在集群的不同的数据节点datanode上,为保正数据的可用及容错,HDFS设计成每个block共有三份,即三个副本

机架: 第一块:在本机器的HDFS目录下存储Block的第一个副本。 第二块:在不同Rack(机架,暂且称为r1)的某个DataNode(称为dn2)上存储Block的第二个副本。 第三块:在dn2所在机架r1下,找一台其它的datanode节点,存储Block的第三个副本。更能多副本:随机节点

HDFS读写流程:

数据写流程:

详细流程

  • 创建文件:

    • HDFS客户端向HDFS写数据,先调用DistributedFileSystem.create()方法,在HDFS创建新的空文件
    • RPC(ClientProtocol.create())远程过程调用NameNode(NameNodeRpcServer)的create(),首先在HDFS目录树指定路径添加新文件
    • 然后将创建新文件的操作记录在editslog中
    • NameNode.create方法执行完后,DistributedFileSystem.create()返回FSDataOutputStream,它本质是封装了一个DFSOutputStream对象
  • 建立数据流管道:

    • 客户端调用DFSOutputStream.write()写数据
    • DFSOutputStream调用ClientProtocol.addBlock(),首先向NameNode申请一个空的数据块
    • addBlock()返回LocatedBlock对象,对象包含当前数据块的所有datanode的位置信息
    • 根据位置信息,建立数据流管道
  • 向数据流管道pipeline中写当前块的数据:

    • 客户端向流管道中写数据,先将数据写入一个检验块chunk中,大小512Byte,写满后,计算chunk的检验和checksum值(4Byte)
    • 然后将chunk数据本身加上checksum,形成一个带checksum值的chunk(516Byte)
    • 保存到一个更大一些的结构packet数据包中,packet为64kB大小
  • packet写满后,先被写入一个dataQueue队列中

    • packet被从队列中取出,向pipeline中写入,先写入datanode1,再从datanoe1传到datanode2,再从datanode2传到datanode3中
  • 一个packet数据取完后,后被放入到ackQueue中等待pipeline关于该packet的ack的反馈

    • 每个packet都会有ack确认包,逆pipeline(dn3 -> dn2 -> dn1)传回输出流
  • 若packet的ack是SUCCESS成功的,则从ackQueue中,将packet删除;否则,将packet从ackQueue中取出,重新放入dataQueue,重新发送

    • 如果当前块写完后,文件还有其它块要写,那么再调用addBlock方法(流程同上
  • 文件最后一个block块数据写完后,会再发送一个空的packet,表示当前block写完了,然后关闭pipeline

    • 所有块写完,close()关闭流
  • ClientProtocol.complete()通知namenode当前文件所有块写完了

容错

  • 在写的过程中,pipeline中的datanode出现故障(如网络不通),输出流如何恢复
    • 输出流中ackQueue缓存的所有packet会被重新加入dataQueue
    • 输出流调用ClientProtocol.updateBlockForPipeline(),为block申请一个新的时间戳,namenode会记录新时间戳
    • 确保故障datanode即使恢复,但由于其上的block时间戳与namenode记录的新的时间戳不一致,故障datanode上的block进而被删除
    • 故障的datanode从pipeline中删除
    • 输出流调用ClientProtocol.getAdditionalDatanode()通知namenode分配新的datanode到数据流pipeline中,并使用新的时间戳建立pipeline
    • 新添加到pipeline中的datanode,目前还没有存储这个新的block,HDFS客户端通过DataTransferProtocol通知pipeline中的一个datanode复制这个block到新的datanode中
    • pipeline重建后,输出流调用ClientProtocol.updatePipeline(),更新namenode中的元数据
    • 故障恢复完毕,完成后续的写入流程

数据读流程:

详细流程

  • 1、client端读取HDFS文件,client调用文件系统对象DistributedFileSystem的open方法
  • 2、返回FSDataInputStream对象(对DFSInputStream的包装)
  • 3、构造DFSInputStream对象时,调用namenode的getBlockLocations方法,获得file的开始若干block(如blk1, blk2, blk3, blk4)的存储datanode(以下简称dn)列表;针对每个block的dn列表,会根据网络拓扑做排序,离client近的排在前;
  • 4、调用DFSInputStream的read方法,先读取blk1的数据,与client最近的datanode建立连接,读取数据
  • 5、读取完后,关闭与dn建立的流
  • 6、读取下一个block,如blk2的数据(重复步骤4、5、6)
  • 7、这一批block读取完后,再读取下一批block的数据(重复3、4、5、6、7)
  • 8、完成文件数据读取后,调用FSDataInputStream的close方法

6.2.2 容错

  • 情况一:读取block过程中,client与datanode通信中断

    • client与存储此block的第二个datandoe建立连接,读取数据
    • 记录此有问题的datanode,不会再从它上读取数据
  • 情况二:client读取block,发现block数据有问题

    • client读取block数据时,同时会读取到block的校验和,若client针对读取过来的block数据,计算检验和,其值与读取过来的校验和不一样,说明block数据损坏
    • client从存储此block副本的其它datanode上读取block数据(也会计算校验和)
    • 同时,client会告知namenode此情况;

联邦

为什么需要联邦

  • 虽然HDFS HA解决了“单点故障”问题,但HDFS在扩展性、整体性能和隔离性方面仍有问题
    • 系统扩展性方面,元数据存储在NN内存中,受限于内存上限(每个文件、目录、block占用约150字节)
    • 整体性能方面,吞吐量受单个NN的影响
    • 隔离性方面,一个程序可能会影响其他程序的运行,如果一个程序消耗过多资源会导致其他程序无法顺利运行
    • HDFS HA本质上还是单名称节点
  • HDFS联邦可以解决以上三个问题
    • HDFS联邦中,设计了多个命名空间;每个命名空间有一个NN或一主一备两个NN,使得HDFS的命名服务能够水平扩展
    • 这些NN分别进行各自命名空间namespace和块的管理,相互独立,不需要彼此协调
    • 每个DN要向集群中所有的NN注册,并周期性的向所有NN发送心跳信息和块信息,报告自己的状态
    • HDFS联邦每个相互独立的NN对应一个独立的命名空间
    • 每一个命名空间管理属于自己的一组块,这些属于同一命名空间的块对应一个“块池”的概念。
    • 每个DN会为所有块池提供块的存储,块池中的各个块实际上是存储在不同DN中的

Sequence Files

  • SequenceFile文件,主要由一条条record记录组成;每个record是键值对形式的
  • SequenceFile文件可以作为小文件的存储容器;
    • 每条record保存一个小文件的内容
    • 小文件名作为当前record的键;
    • 小文件的内容作为当前record的值;
    • 如10000个100KB的小文件,可以编写程序将这些文件放到一个SequenceFile文件。
  • 一个SequenceFile是可分割的,所以MapReduce可将文件切分成块,每一块独立操作。

命令行

2、分布式计算MapReduce

MapReduce是采用一种分而治之的思想设计出来的分布式计算框架

  • MapReduce由两个阶段组成:
    • Map阶段(切分成一个个小的任务)
    • Reduce阶段(汇总小任务的结果)

MapReduce编程模型

  • Map阶段

    • 假设MR的输入文件“Gone With The Wind”有三个block;block1、block2、block3
    • MR编程时,每个block对应一个分片split
    • 每一个split对应一个map任务(map task)
    • 如图共3个map任务(map1、map2、map3);这3个任务的逻辑一样,所以以第一个map任务(map1)为例分析
    • map1读取block1的数据;一次读取block1的一行数据;
      • 产生键值对(key/value),作为map()的参数传入,调用map();
      • 假设当前所读行是第一行
      • 将当前所读行的行首相对于当前block开始处的字节偏移量作为key(0)
      • 当前行的内容作为value(Dear Bear River)
    • map()内
      • (按需求,写业务代码),将value当前行内容按空格切分,得到三个单词Dear | Bear | River
      • 将每个单词变成键值对,输出出去(Dear, 1) | (Bear, 1) | (River, 1);最终结果写入map任务所在节点的本地磁盘中(内里还有细节,讲到shuffle时,再细细展开)
      • block的第一行的数据被处理完后,接着处理第二行;逻辑同上
      • 当map任务将当前block中所有的数据全部处理完后,此map任务即运行结束
    • 其它的每一个map任务都是如上逻辑,不再赘述
  • Reduce阶段

    • reduce任务(reduce task)的个数由自己写的程序编程指定,main()内的job.setNumReduceTasks(4)指定reduce任务是4个(reduce1、reduce2、reduce3、reduce4)
    • 每一个reduce任务的逻辑一下,所以以第一个reduce任务(reduce1)为例分析
    • map1任务完成后,reduce1通过网络,连接到map1,将map1输出结果中属于reduce1的分区的数据,通过网络获取到reduce1端(拷贝阶段)
    • 同样也如此连接到map2、map3获取结果
    • 最终reduce1端获得4个(Dear, 1)键值对;由于key键相同,它们分到同一组;
    • 4个(Dear, 1)键值对,转换成[Dear, Iterable(1, 1, 1, )],作为两个参数传入reduce()
    • 在reduce()内部,计算Dear的总数为4,并将(Dear, 4)作为键值对输出
    • 每个reduce任务最终输出文件(内里还有细节,讲到shuffle时,再细细展开),文件写入到HDFS!

    2.7 总结

  • MR分为两个阶段:map阶段、reduce阶段

  • MR输入的文件有几个block,就会生成几个map任务

  • MR的reduce任务的个数,由程序中编程指定:job.setNumReduceTasks(4)

  • map任务

    • map任务中map()一次读取block的一行数据,以kv对的形式输入map()
    • map()的输出作为reduce()的输入
  • reduce任务

    • reduce任务通过网络将各执行完成的map任务输出结果中,属于自己的数据取过来
    • key相同的键值对作为一组,调用一次reduce()
    • reduce任务生成一个结果文件
    • 文件写入HDFS shuffle
  • shuffle主要指的是map端的输出作为reduce端输入的过程 !

6.3 map端

  • 每个map任务都有一个对应的环形内存缓冲区;输出是kv对,先写入到环形缓冲区(默认大小100M),当内容占据80%缓冲区空间后,由一个后台线程将缓冲区中的数据溢出写到一个磁盘文件
  • 在溢出写的过程中,map任务可以继续向环形缓冲区写入数据;但是若写入速度大于溢出写的速度,最终造成100m占满后,map任务会暂停向环形缓冲区中写数据的过程;只执行溢出写的过程;直到环形缓冲区的数据全部溢出写到磁盘,才恢复向缓冲区写入
  • 后台线程溢写磁盘过程,有以下几个步骤:
    • 先对每个溢写的kv对做分区;分区的个数由MR程序的reduce任务数决定;默认使用HashPartitioner计算当前kv对属于哪个分区;计算公式:(key.hashCode() & Integer.MAX_VALUE) % numReduceTasks
    • 每个分区中,根据kv对的key做内存中排序;
    • 若设置了map端本地聚合combiner,则对每个分区中,排好序的数据做combine操作;
    • 若设置了对map输出压缩的功能,会对溢写数据压缩
  • 随着不断的向环形缓冲区中写入数据,会多次触发溢写(每当环形缓冲区写满100m),本地磁盘最终会生成多个溢出文件
  • 合并溢写文件:在map task完成之前,所有溢出文件会被合并成一个大的溢出文件;且是已分区、已排序的输出文件
  • 小细节:
    • 在合并溢写文件时,如果至少有3个溢写文件,并且设置了map端combine的话,会在合并的过程中触发combine操作;
    • 但是若只有2个或1个溢写文件,则不触发combine操作(因为combine操作,本质上是一个reduce,需要启动JVM虚拟机,有一定的开销)

6.4 reduce端

  • reduce task会在每个map task运行完成后,通过HTTP获得map task输出中,属于自己的分区数据(许多kv对)

  • 如果map输出数据比较小,先保存在reduce的jvm内存中,否则直接写入reduce磁盘

  • 一旦内存缓冲区达到阈值(默认0.66)或map输出数的阈值(默认1000),则触发归并merge,结果写到本地磁盘

  • 若MR编程指定了combine,在归并过程中会执行combine操作

  • 随着溢出写的文件的增多,后台线程会将它们合并大的、排好序的文件

  • reduce task将所有map task复制完后,将合并磁盘上所有的溢出文件

  • 默认一次合并10个

  • 最后一批合并,部分数据来自内存,部分来自磁盘上的文件

  • 进入“归并、排序、分组阶段”

  • 每组数据调用一次reduce方法

6.5 总结

  • map端
    • map()输出结果先写入环形缓冲区
    • 缓冲区100M;写满80M后,开始溢出写磁盘文件
    • 此过程中,会进行分区、排序、combine(可选)、压缩(可选)
    • map任务完成前,会将多个小的溢出文件,合并成一个大的溢出文件(已分区、排序)
  • reduce端
    • 拷贝阶段:reduce任务通过http将map任务属于自己的分区数据拉取过来
    • 开始merge及溢出写磁盘文件
    • 所有map任务的分区全部拷贝过来后,进行阶段合并、排序、分组阶段
    • 每组数据调用一次reduce()
    • 结果写入HDFS

3、yarn资源调度系统

  • 类似HDFS,YARN也是经典的主从(master/slave)架构
    • YARN服务由一个ResourceManager(RM)和多个NodeManager(NM)构成
    • ResourceManager为主节点(master)
    • NodeManager为从节点(slave)

基本组件

ResourceManager

  • RM是一个全局的资源管理器,集群只有一个

    • 负责整个系统的资源管理和分配
    • 包括处理客户端请求
    • 启动/监控 ApplicationMaster
    • 监控 NodeManager、资源的分配与调度
  • 它主要由两个组件构成:

    • 调度器(Scheduler)
    • 应用程序管理器(Applications Manager,ASM)
  • 调度器

    • 调度器根据容量、队列等限制条件(如每个队列分配一定的资源,最多执行一定数量的作业等),将系统中的资源分配给各个正在运行的应用程序。
    • 需要注意的是,该调度器是一个“纯调度器”
      • 它不从事任何与具体应用程序相关的工作,比如不负责监控或者跟踪应用的执行状态等,也不负责重新启动因应用执行失败或者硬件故障而产生的失败任务,这些均交由应用程序相关的ApplicationMaster完成。
      • 调度器仅根据各个应用程序的资源需求进行资源分配,而资源分配单位用一个抽象概念“资源容器”(Resource Container,简称Container)表示,Container是一个动态资源分配单位,它将内存、CPU、磁盘、网络等资源封装在一起,从而限定每个任务使用的资源量。
  • 应用程序管理器

    • 应用程序管理器主要负责管理整个系统中所有应用程序
    • 接收job的提交请求
    • 为应用分配第一个 Container 来运行 ApplicationMaster,包括应用程序提交、与调度器协商资源以启动 ApplicationMaster、监控 ApplicationMaster 运行状态并在失败时重新启动它等

NodeManager

  • NodeManager 是一个 slave 服务,整个集群有多个

  • NodeManager :

    • 它负责接收 ResourceManager 的资源分配请求,分配具体的 Container 给应用。
    • 负责监控并报告 Container 使用信息给 ResourceManager。
  • 功能:

    • NodeManager 本节点上的资源使用情况和各个 Container 的运行状态(cpu和内存等资源)

    • 接收及处理来自 ResourceManager 的命令请求,分配 Container 给应用的某个任务;

    • 定时地向RM汇报以确保整个集群平稳运行,RM 通过收集每个 NodeManager 的报告信息来追踪整个集群健康状态的,而 NodeManager 负责监控自身的健康状态;

    • 处理来自 ApplicationMaster 的请求;

    • 管理着所在节点每个 Container 的生命周期;

    • 管理每个节点上的日志;

    • 当一个节点启动时,它会向 ResourceManager 进行注册并告知 ResourceManager 自己有多少资源可用。

    • 在运行期,通过 NodeManager 和 ResourceManager 协同工作,这些信息会不断被更新并保障整个集群发挥出最佳状态。

    • NodeManager 只负责管理自身的 Container,它并不知道运行在它上面应用的信息。负责管理应用信息的组件是 ApplicationMaster

Container

  • Container 是 YARN 中的资源抽象

    • 它封装了某个节点上的多维度资源,如内存、CPU、磁盘、网络等
    • 当 AM 向 RM 申请资源时,RM 为 AM 返回的资源便是用 Container 表示的。
    • YARN 会为每个任务分配一个 Container,且该任务只能使用该 Container 中描述的资源。
  • Container 和集群NodeManager节点的关系是:

    • 一个NodeManager节点可运行多个 Container
    • 但一个 Container 不会跨节点。
    • 任何一个 job 或 application 必须运行在一个或多个 Container 中
    • 在 Yarn 框架中,ResourceManager 只负责告诉 ApplicationMaster 哪些 Containers 可以用
    • ApplicationMaster 还需要去找 NodeManager 请求分配具体的 Container。
  • 需要注意的是

    • Container 是一个动态资源划分单位,是根据应用程序的需求动态生成的
    • 目前为止,YARN 仅支持 CPU 和内存两种资源,且使用了轻量级资源隔离机制 Cgroups 进行资源隔离。
  • 功能:

    • 对task环境的抽象;

    • 描述一系列信息;

    • 任务运行资源的集合(cpu、内存、io等);

    • 任务运行环境

ApplicationMaster

  • 功能:

    • 数据切分;
    • 为应用程序申请资源并进一步分配给内部任务(TASK);
    • 任务监控与容错;
    • 负责协调来自ResourceManager的资源,并通过NodeManager监视容器的执行和资源使用情况。
  • ApplicationMaster 与 ResourceManager 之间的通信

    • 是整个 Yarn 应用从提交到运行的最核心部分,是 Yarn 对整个集群进行动态资源管理的根本步骤
    • Yarn 的动态性,就是来源于多个Application 的 ApplicationMaster 动态地和 ResourceManager 进行沟通,不断地申请、释放、再申请、再释放资源的过程。 yarn提交过程
    • Application在Yarn中的执行过程,整个执行过程可以总结为三步:
    • 应用程序提交
    • 启动应用的ApplicationMaster实例
    • ApplicationMaster 实例管理应用程序的执行
  • 客户端程序向 ResourceManager 提交应用,并请求一个 ApplicationMaster 实例;

  • ResourceManager 找到一个可以运行一个 Container 的 NodeManager,并在这个 Container 中启动 ApplicationMaster 实例;

  • ApplicationMaster 向 ResourceManager 进行注册,注册之后客户端就可以查询 ResourceManager 获得自己 ApplicationMaster 的详细信息,以后就可以和自己的 ApplicationMaster 直接交互了(这个时候,客户端主动和 ApplicationMaster 交流,应用先向 ApplicationMaster 发送一个满足自己需求的资源请求);

  • ApplicationMaster 根据 resource-request协议 向 ResourceManager 发送 resource-request请求;

  • 当 Container 被成功分配后,ApplicationMaster 通过向 NodeManager 发送 container-launch-specification信息 来启动Container,container-launch-specification信息包含了能够让Container 和 ApplicationMaster 交流所需要的资料;

  • 应用程序的代码以 task 形式在启动的 Container 中运行,并把运行的进度、状态等信息通过 application-specific协议 发送给ApplicationMaster;

  • 在应用程序运行期间,提交应用的客户端主动和 ApplicationMaster 交流获得应用的运行状态、进度更新等信息,交流协议也是 application-specific协议;

  • 应用程序执行完成并且所有相关工作也已经完成,ApplicationMaster 向 ResourceManager 取消注册然后关闭,用到所有的 Container 也归还给系统。

  • 精简版的:

    • 步骤1:用户将应用程序提交到 ResourceManager 上;
    • 步骤2:ResourceManager为应用程序 ApplicationMaster 申请资源,并与某个 NodeManager 通信启动第一个 Container,以启动ApplicationMaster;
    • 步骤3:ApplicationMaster 与 ResourceManager 注册进行通信,为内部要执行的任务申请资源,一旦得到资源后,将于 NodeManager 通信,以启动对应的 Task;
    • 步骤4:所有任务运行完成后,ApplicationMaster 向 ResourceManager 注销,整个应用程序运行结束。

MapReduce on YARN

  • 提交作业

    • ①程序打成jar包,在客户端运行hadoop jar命令,提交job到集群运行
    • job.waitForCompletion(true)中调用Job的submit(),此方法中调用JobSubmitter的submitJobInternal()方法;
      • ②submitClient.getNewJobID()向resourcemanager请求一个MR作业id
      • 检查输出目录:如果没有指定输出目录或者目录已经存在,则报错
      • 计算作业分片;若无法计算分片,也会报错
      • ③运行作业的相关资源,如作业的jar包、配置文件、输入分片,被上传到HDFS上一个以作业ID命名的目录(jar包副本默认为10,运行作业的任务,如map任务、reduce任务时,可从这10个副本读取jar包)
      • ④调用resourcemanager的submitApplication()提交作业
    • 客户端每秒查询一下作业的进度(map 50% reduce 0%),进度如有变化,则在控制台打印进度报告;
    • 作业如果成功执行完成,则打印相关的计数器
    • 但如果失败,在控制台打印导致作业失败的原因(要学会查看日志,定位问题,分析问题,解决问题)
  • 初始化作业

    • 当ResourceManager(一下简称RM)收到了submitApplication()方法的调用通知后,请求传递给RM的scheduler(调度器);调度器分配container(容器)
    • ⑤a RM与指定的NodeManager通信,通知NodeManager启动容器;NodeManager收到通知后,创建占据特定资源的container;
    • ⑤b 然后在container中运行MRAppMaster进程
    • ⑥MRAppMaster需要接受任务(各map任务、reduce任务的)的进度、完成报告,所以appMaster需要创建多个簿记对象,记录这些信息
    • ⑦从HDFS获得client计算出的输入分片split
      • 每个分片split创建一个map任务
      • 通过 mapreduce.job.reduces 属性值(编程时,jog.setNumReduceTasks()指定),知道当前MR要创建多少个reduce任务
      • 每个任务(map、reduce)有task id
  • Task 任务分配

    • 如果小作业,appMaster会以uberized的方式运行此MR作业;appMaster会决定在它的JVM中顺序此MR的任务;

      • 原因是,若每个任务运行在一个单独的JVM时,都需要单独启动JVM,分配资源(内存、CPU),需要时间;多个JVM中的任务再在各自的JVM中并行运行

      • 若将所有任务在appMaster的JVM中顺序执行的话,更高效,那么appMaster就会这么做 ,任务作为uber任务运行

      • 小作业判断依据:①小于10个map任务;②只有一个reduce任务;③MR输入大小小于一个HDFS块大小

      • 如何开启uber?设置属性 mapreduce.job.ubertask.enable 值为true

        configuration.set("mapreduce.job.ubertask.enable", "true");
        
      • 在运行任何task之前,appMaster调用setupJob()方法,创建OutputCommitter,创建作业的最终输出目录(一般为HDFS上的目录)及任务输出的临时目录(如map任务的中间结果输出目录)

    • ⑧若作业不以uber任务方式运行,那么appMaster会为作业中的每一个任务(map任务、reduce任务)向RM请求container

      • 由于reduce任务在进入排序阶段之前,所有的map任务必须执行完成;所以,为map任务申请容器要优先于为reduce任务申请容器

      • 5%的map任务执行完成后,才开始为reduce任务申请容器

      • 为map任务申请容器时,遵循数据本地化,调度器尽量将容器调度在map任务的输入分片所在的节点上(移动计算,不移动数据)

      • reduce任务能在集群任意计算节点运行

      • 默认情况下,为每个map任务、reduce任务分配1G内存、1个虚拟内核,由属性决定mapreduce.map.memory.mb、mapreduce.reduce.memory.mb、mapreduce.map.cpu.vcores、mapreduce.reduce.reduce.cpu.vcores

  • Task 任务执行

    • 当调度器为当前任务分配了一个NodeManager(暂且称之为NM01)的容器,并将此信息传递给appMaster后;appMaster与NM01通信,告知NM01启动一个容器,并此容器占据特定的资源量(内存、CPU)
    • NM01收到消息后,启动容器,此容器占据指定的资源量
    • 容器中运行YarnChild,由YarnChild运行当前任务(map、reduce)
    • ⑩在容器中运行任务之前,先将运行任务需要的资源拉取到本地,如作业的JAR文件、配置文件、分布式缓存中的文件
  • 作业运行进度与状态更新

    • 作业job以及它的每个task都有状态(running、successfully completed、failed),当前任务的运行进度、作业计数器
    • 任务在运行期间,每隔3秒向appMaster汇报执行进度、状态(包括计数器)
    • appMaster汇总目前运行的所有任务的上报的结果
    • 客户端每个1秒,轮询访问appMaster获得作业执行的最新状态,若有改变,则在控制台打印出来
  • 完成作业

    • appMaster收到最后一个任务完成的报告后,将作业状态设置为成功
    • 客户端轮询appMaster查询进度时,发现作业执行成功,程序从waitForCompletion()退出
    • 作业的所有统计信息打印在控制台
    • appMaster及运行任务的容器,清理中间的输出结果
    • 作业信息被历史服务器保存,留待以后用户查询

3.3 yarn应用生命周期

  • RM: Resource Manager
  • AM: Application Master
  • NM: Node Manager
  1. Client向RM提交应用,包括AM程序及启动AM的命令。
  2. RM为AM分配第一个容器,并与对应的NM通信,令其在容器上启动应用的AM。
  3. AM启动时向RM注册,允许Client向RM获取AM信息然后直接和AM通信。
  4. AM通过资源请求协议,为应用协商容器资源。
  5. 如容器分配成功,AM要求NM在容器中启动应用,应用启动后可以和AM独立通信。
  6. 应用程序在容器中执行,并向AM汇报。
  7. 在应用执行期间,Client和AM通信获取应用状态。
  8. 应用执行完成,AM向RM注销并关闭,释放资源。

申请资源->启动appMaster->申请运行任务的container->分发Task->运行Task->Task结束->回收container

调服器

5. YARN调度器

  • 试想一下,你现在所在的公司有一个hadoop的集群。但是A项目组经常做一些定时的BI报表,B项目组则经常使用一些软件做一些临时需求。那么他们肯定会遇到同时提交任务的场景,这个时候到底如何分配资源满足这两个任务呢?是先执行A的任务,再执行B的任务,还是同时跑两个?

  • 在Yarn框架中,调度器是一块很重要的内容。有了合适的调度规则,就可以保证多个应用可以在同一时间有条不紊的工作。最原始的调度规则就是FIFO,即按照用户提交任务的时间来决定哪个任务先执行,先提交的先执行。但是这样很可能一个大任务独占资源,其他的资源需要不断的等待。也可能一堆小任务占用资源,大任务一直无法得到适当的资源,造成饥饿。所以FIFO虽然很简单,但是并不能满足我们的需求。

  • 理想情况下,yarn应用发出的资源请求应该立刻给予满足。然而现实中的资源有限,在一个繁忙的集群上,一个应用经常需要等待才能得到所需的资源。yarn调度器的工作就是根据既定的策略为应用分配资源。调度通常是一个难题,并且没有一个所谓的“最好”的策略,这也是为什么yarn提供了多重调度器和可配置策略供我们选择的原因。

yarn分为一级调度管理和二级调度管理 一级调度管理(更近底层,更接近于操作资源, 更偏向于应用层和底层结合) 计算资源管理(cpu,内存等,计算复杂消耗的cpu多) App生命周期管理 二级调度管理(自己代码的算法等, 更偏向于应用层) App内部的计算模型管理 多样化的计算模型

5.1 调度器

  • 在YARN中有三种调度器可以选择:FIFO Scheduler ,Capacity Scheduler,FairS cheduler

三种调度模型

5.2 FIFO Scheduler

  • FIFO Scheduler把应用按提交的顺序排成一个队列,这是一个先进先出队列,在进行资源分配的时候,先给队列中最头上的应用进行分配资源,待最头上的应用需求满足后再给下一个分配,以此类推。

  • FIFO Scheduler是最简单也是最容易理解的调度器,也不需要任何配置,但它并不适用于共享集群。大的应用可能会占用所有集群资源,这就导致其它应用被阻塞。在共享集群中,更适合采用Capacity Scheduler或Fair Scheduler,这两个调度器都允许大任务和小任务在提交的同时获得一定的系统资源。

  • 上图展示了这几个调度器的区别,从图中可以看出,在FIFO 调度器中,小任务会被大任务阻塞。

5.3 Capacity Scheduler

  • CDH版本默认使用Fair Scheduler公平调度器

5.4 Fair Scheduler

  • Apache Hadoop默认使用Capacity Scheduler容量调度器

  • CDH版本默认使用Fair Scheduler公平调度器