MapReduce程序运行流程分析
- 1)在MapReduce程序读取文件的输入目录上存放相应的文件。
- 2)客户端程序在submit()方法执行前,获取待处理的数据信息,然后根据集群中参数的配置形成一个任务分配规划。
- 3)客户端提交job.split、jar包、job.xml等文件给yarn,yarn中的resourcemanager启动MRAppMaster。
- 4)MRAppMaster启动后根据本次job的描述信息,计算出需要的maptask实例数量,然后向集群申请机器启动相应数量的maptask进程。
- 5)maptask利用客户指定的inputformat来读取数据,形成输入KV对。
- 6)maptask将输入KV对传递给客户定义的map()方法,做逻辑运算
- 7)map()运算完毕后将KV对收集到maptask缓存。
- 8)maptask缓存中的KV对按照K分区排序后不断写到磁盘文件
- 9)MRAppMaster监控到所有maptask进程任务完成之后,会根据客户指定的参数启动相应数量的reducetask进程,并告知reducetask进程要处理的数据分区。
- 10)Reducetask进程启动之后,根据MRAppMaster告知的待处理数据所在位置,从若干台maptask运行所在机器上获取到若干个maptask输出结果文件,并在本地进行重新归并排序,然后按照相同key的KV为一个组,调用客户定义的reduce()方法进行逻辑运算。
- 11)Reducetask运算完毕后,调用客户指定的outputformat将结果数据输出到外部存储。
Shuffle机制
- mapreduce中,map阶段处理的数据是如何传递给reduce阶段,是MapReduce框架中最关键的一个流程,这个流程就叫shuffle;
- shuffle:洗牌、发牌(核心机制:数据分区、排序、缓存);
- 具体来说:就是将maptask输出的处理结果数据,分发给reducetask,并在分发的过程中,对数据按key进行了分区和排序。
MapReduce详细工作流程
- 上面图片中,橙色方框的地方就是在MapReduce编程中可以自定义的地方,也就是可以自己用代码控制的地方;
- 上面的流程是整个mapreduce最全工作流程,但是shuffle过程只是从第7步开始到第16步结束,具体shuffle过程详解,如下:
- 1)maptask收集我们的map()方法输出的kv对,放到内存缓冲区中
- 2)从内存缓冲区不断溢出本地磁盘文件,可能会溢出多个文件
- 3)多个溢出文件会被合并成大的溢出文件
- 4)在溢出过程中,及合并的过程中,都要调用partitoner进行分组和针对key进行排序
- 5)reducetask根据自己的分区号,去各个maptask机器上取相应的结果分区数据
- 6)reducetask会取到同一个分区的来自不同maptask的结果文件,reducetask会将这些文件再进行合并(归并排序)
- 7)合并成大文件后,shuffle的过程也就结束了,后面进入reducetask的逻辑运算过程(从文件中取出一个一个的键值对group,调用用户自定义的reduce()方法)
- 注意:Shuffle中的缓冲区大小会影响到mapreduce程序的执行效率,原则上说,缓冲区越大,磁盘io的次数越少,执行速度就越快。
缓冲区的大小可以通过参数调整,参数:io.sort.mb 默认100M
数据切片机制
- 默认情况下,每一个输入文件单独切片。切片大小=blocksize=128M。每次切片时,都要判断切完剩下的部分是否大于块(128M)的1.1倍,不大于块(128M)的1.1倍就划分一块切片。比如一个257M的文件,就切为2块:128M + 129M
- 将切片信息写到一个切片规划文件job.split中,数据切片只是在逻辑上对输入数据进行分片,并不会在磁盘上将其切分成分片进行存储。InputSplit只记录了分片的元数据信息,比如起始位置、长度以及所在的节点列表等。
- block是HDFS上物理上存储的存储的数据,切片是对数据逻辑上的划分。
- 提交切片规划文件job.split到yarn上,yarn上的MrAppMaster就可以根据切片规划文件计算开启maptask个数。
MapTask工作机制
- 1)问题引出
- maptask的并行度决定map阶段的任务处理并发度,进而影响到整个job的处理速度。那么,mapTask并行任务是否越多越好呢?
- 2)MapTask并行度决定机制
- 一个job的map阶段MapTask并行度(个数),由客户端提交job时的切片个数决定。
- 3)MapTask工作机制
- (1)Read阶段:Map Task通过用户编写的RecordReader,从输入InputSplit中解析出一个个key/value。
- (2)Map阶段:该节点主要是将解析出的key/value交给用户编写map()函数处理,并产生一系列新的key/value。
- (3)Collect阶段:在用户编写map()函数中,当数据处理完成后,一般会调用OutputCollector.collect()输出结果。在该函数内部,它会将生成的key/value分区(调用Partitioner),并写入一个环形内存缓冲区中(默认100M)。
- (4)Spill阶段:即“溢写”,当环形缓冲区满后,MapReduce会将数据写到本地磁盘上,生成一个临时文件。需要注意的是,将数据写入本地磁盘之前,先要对数据进行一次本地排序,并在必要时对数据进行合并、压缩等操作。先按照分区编号partition进行排序,然后按照key进行排序。这样同一分区内所有数据按照key有序。
- (5)Combine阶段:当所有数据处理完成后,MapTask对所有临时文件进行一次合并,以确保最终只会生成一个数据文件。在进行文件合并过程中,MapTask以分区为单位进行合并。每个MapTask最终只生成一个数据文件,可避免同时打开大量文件和同时读取大量小文件产生的随机读取带来的开销。
ReduceTask工作机制
- 1)设置ReduceTask
- reducetask的并行度同样影响整个job的执行并发度和执行效率,但与maptask的并发数由切片数决定不同,Reducetask数量的决定是可以直接手动设置,默认值是1;
- 2)注意
- (1)如果数据分布不均匀,就有可能在reduce阶段产生数据倾斜
- (2)reducetask数量并不是任意设置,还要考虑业务逻辑需求,有些情况下,需要计算全局汇总结果,就只能有1个reducetask。
- (3)具体多少个reducetask,需要根据集群性能而定。
- (4)如果分区数不是1,但是reducetask为1,是否执行分区过程。答案是:不执行分区过程。因为在maptask的源码中,执行分区的前提是先判断reduceNum个数是否大于1。不大于1肯定不执行。
- 3)ReduceTask工作机制
- (1)Copy阶段:ReduceTask从各个MapTask上远程拷贝一片数据,并针对某一片数据,如果其大小超过一定阈值,则写到磁盘上,否则直接放到内存中。
- (2)Merge阶段:在远程拷贝数据的同时,ReduceTask启动了两个后台线程对内存和磁盘上的文件进行合并,以防止内存使用过多或磁盘上文件过多。
- (3)Sort阶段:按照MapReduce语义,用户编写reduce()函数输入数据是按key进行聚集的一组数据。为了将key相同的数据聚在一起,Hadoop采用了基于排序的策略。由于各个MapTask已经实现对自己的处理结果进行了局部排序,因此,ReduceTask只需对所有数据进行一次归并排序即可。
- (4)Reduce阶段:reduce()函数将计算结果写到HDFS上。
- Map阶段假如入有2个分区(调用Partitioner),同一分区内所有数据按照key有序(调用compareTo排序),Reduce阶段reducetask数量也设置为2,每个reducetask分别处理一个分区,将相同key(通过GroupingComparator分组)的数据聚在一起,最终计算结果也会形成2个文件。
Yarn工作机制
- 1)Yarn并不清楚用户提交的程序的运行机制
- 2)Yarn只提供运算资源的调度(用户程序向Yarn申请资源,Yarn就负责分配资源)
- 3)Yarn中的主管角色叫ResourceManager
- 4)Yarn中具体提供运算资源的角色叫NodeManager
- 5)这样一来,Yarn其实就与运行的用户程序完全解耦,就意味着Yarn上可以运行各种类型的分布式运算程序(mapreduce只是其中的一种),比如mapreduce、storm程序,spark程序……
- 6)所以,spark、storm等运算框架都可以整合在Yarn上运行,只要他们各自的框架中有符合Yarn规范的资源请求机制即可
- 7)Yarn就成为一个通用的资源调度平台,从此,企业中以前存在的各种运算集群都可以整合在一个物理集群上,提高资源利用率,方便数据共享
- (1)Mr程序提交到客户端所在的节点
- (2)yarnrunner向Resourcemanager申请一个application。
- (3)rm将该应用程序的资源路径返回给yarnrunner
- (4)该程序将运行所需资源提交到HDFS上
- (5)程序资源提交完毕后,申请运行mrAppMaster
- (6)RM将用户的请求初始化成一个task
- (7)其中一个NodeManager领取到task任务。
- (8)该NodeManager创建容器Container,并产生MRAppmaster
- (9)Container从HDFS上拷贝资源到本地
- (10)MRAppmaster向RM 申请运行maptask容器
- (11)RM将运行maptask任务分配给另外两个NodeManager,另两个NodeManager分别领取任务并创建容器。
- (12)MR向两个接收到任务的NodeManager发送程序启动脚本,这两个NodeManager分别启动maptask,maptask对数据分区排序。
- (13)MRAppmaster向RM申请2个容器,运行reduce task。
- (14)reduce task向maptask获取相应分区的数据。
- (15)程序运行完毕后,MR会向RM注销自己。
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()方法
- (3)Combiner
- combiner是在每一个maptask所在的节点运行,对每一个maptask的输出进行局部汇总,以减小网络传输量
- (1)Partitioner
- 4)reduce端的数据分组比较接口:Groupingcomparator
- reduceTask拿到输入数据(一个partition的所有数据)后,首先需要对数据进行分组,其分组的默认原则是key相同,然后对每一组kv数据调用一次reduce()方法,并且将这一组kv中的第一个kv的key作为参数传给reduce的key,将这一组数据的value的迭代器传给reduce()的values参数
- 5)逻辑处理接口:Reducer
- 完全需要用户自己去实现其中 reduce() setup() clean()
- 6)输出数据接口:OutputFormat—> 有一系列子类FileOutputformat、DBoutputFormat …
- 默认实现类是TextOutputFormat,功能逻辑是:将每一个KV对向目标文本文件中输出为一行