此文百分之七十摘自我认为讲的很清楚的博客,我都贴了地址,很感谢这些博主的无私奉献!我再将一些自己的实例代码和知识点的补充加入进去,希望能更好的理解mapreduce的整个过程。
From:MapReduce工作原理图文详解,JobTracker和TaskTracker概述
规范是:
按照这样的规范进行讲解
在客户端启动一个作业。拿个比方说,我提交了一个hive程序
向JobTracker请求一个Job ID,就像你排队买车一样,你不得摇个号啊,没有这个号你就不能买车(执行任务)。
将运行作业所需要的资源文件复制到HDFS上,包括MapReduce程序打包的JAR文件、配置文件和客户端计算所得的输入划分信息。这些文件都存放在JobTracker专门为该作业创建的文件夹中。文件夹名为该作业的Job ID。JAR文件默认会有10个副本(mapred.submit.replication属性控制);输入划分信息(Split)告诉了JobTracker应该为这个作业启动多少个map任务等信息。
JobTracker接收到作业后,将其放在一个作业队列里(一般来说,公司部门都与自己的队列,默认的调度方法是FIFO,也就是first in first out-队列),等待作业调度器对其进行调度,当作业调度器根据自己的调度算法调度到该作业时,会根据输入划分信息(Split)为每个划分创建一个map任务,并将map任务分配给TaskTracker执行。对于map和reduce任务,TaskTracker根据主机核的数量和内存的大小有固定数量的map槽和reduce槽。这里需要强调的是:map任务不是随随便便地分配给某个TaskTracker的,这里有个概念叫:数据本地化(Data-Local)。意思是:将map任务分配给含有该map处理的数据块的TaskTracker上,同时将程序JAR包复制到该TaskTracker上来运行,这叫“运算移动,数据不移动”。而分配reduce任务时并不考虑数据本地化。
TaskTracker每隔一段时间会给JobTracker发送一个心跳,告诉JobTracker它依然在运行,同时心跳中还携带着很多的信息,比如当前map任务完成的进度等信息。当JobTracker收到作业的最后一个任务完成信息时,便把该作业设置成“成功”。当JobClient查询状态时,它将得知任务已完成,便显示一条消息给用户。
相较于1.x的版本,目前绝大多数公司用的都是基于2.x的版本,很大的区别就在于使用了Yarn作为了资源管理器,可以使不同的计算框架运行与同一个资源调度器下,而且也解决了1.x版本中JobTracker压力过大,无法扩展及NameNode单点故障等问题。
首先阐述几个重要概念
From:https://blog.csdn.net/lb812913059/article/details/79897863
通过submit或者waitForCompletion提交作业,waitForCompletion()方法通过每秒循环轮转作业进度如果发现与上次报告有改变,则将进度报告发送到控制台
向ResourceManager申请Application ID,RM检查输入输出说明、计算输入分片
复制作业的资源文件,将作业信息(jar、配置文件、分片信息)复制到HDFS上用户的应用缓存目录中
通过submitApplication()方法提交作业到资源管理器
资源管理器在收到submitApplication()消息后,将请求传递给调度器(Scheduler)
调度器为其分配一个容器Container,然后RM在NM的管理下在container中启动程序的ApplicationMaster进程
ApplicationMaster对作业进行初始化,创建过个薄记对象以跟踪作业进度
是一个java应用程序,他的主类是MRAppmaster
ApplicationMaster接受来自HDFS在客户端计算的输入分片
对每一个分片创建一个map任务,任务对象,由mapreduce.job.reduces属性设置reduce个数
如果作业不适合uber任务运行,ApplicationMaster就会为所有的map任务和reduce任务向资源管理器申请容器
请求为任务指定内存需求,map任务和reduce任务的默认都会申请1024MB的内存
资源管理器为任务分配了容器,ApplicationMaster就通过节点管理器启动容器。
该任务由主类YarnChild的java应用程序执行。
运行任务之前,首先将资源本地化,包括作业配置、jar文件和所有来自分布式缓存的文件
最后执行map任务和reduce任务
用人话描述大致描述一下
首先Client向ResourceManager (RM)提交一个Application,RM找了下下资源比较丰富的NodeManager(NM),要求他开辟一个container来启动ApplicationMaster(AM), AM收集到启动任务需要用到的资源量(如申请的map的个数依赖于Input Split的大小),将所需要的资源量向RM提交,RM通过一个资源列表的方式选择一些资源相对丰富的NM返回,告诉AM哪一些节点NM可用启动任务,AM开始和NM进行通信,告知启动对应的map/reduce任务;NM开辟一些列的container来执行这些任务,和AM保持通信。一旦所有任务执行结束,AM向Client输出结果并向RM注销自己
以提交一个完整的mapreduce任务来演示,这里用hive
hive> set mapred.job.queue.name=root.xxxxxxxxx;
> insert overwrite table query_result
> select * from A join B on A.name=B.name; # 这里是提交一个job,这里提交hive任务
Query ID = xx_20161216130626_40fda9ef-386f-4ef3-9e13-7b08ad69118c # 申请资源
Total jobs = 3
Launching Job 1 out of 3
Number of reduce tasks not specified. Estimated from input data size: 1 # reduce个数如果没有设置,那就根据split来确定
In order to change the average load for a reducer (in bytes):
set hive.exec.reducers.bytes.per.reducer=<number>
In order to limit the maximum number of reducers:
set hive.exec.reducers.max=<number>
In order to set a constant number of reducers:
set mapreduce.job.reduces=<number>
Starting Job = job_1481285758114_429080, Tracking URL = http://bigdata-hdp-apache500.xg01:8088/proxy/application_1481285758114_429080/
Kill Command = /usr/local/hadoop-2.7.2/bin/hadoop job -kill job_1481285758114_429080
Hadoop job information for Stage-1: number of mappers: 2; number of reducers: 1
2016-12-16 13:06:39,518 Stage-1 map = 0%, reduce = 0%
2016-12-16 13:06:50,846 Stage-1 map = 50%, reduce = 0%, Cumulative CPU 3.7 sec
2016-12-16 13:06:55,987 Stage-1 map = 100%, reduce = 0%, Cumulative CPU 11.24 sec
2016-12-16 13:07:13,418 Stage-1 map = 100%, reduce = 67%, Cumulative CPU 12.53 sec
2016-12-16 13:07:28,715 Stage-1 map = 100%, reduce = 82%, Cumulative CPU 16.31 sec
2016-12-16 13:07:58,252 Stage-1 map = 100%, reduce = 100%, Cumulative CPU 17.7 sec
MapReduce Total cumulative CPU time: 17 seconds 700 msec
Ended Job = job_1481285758114_429080
Stage-4 is selected by condition resolver.
Stage-3 is filtered out by condition resolver.
Stage-5 is filtered out by condition resolver.
Moving data to: hdfs://mycluster-tj/tmp/hive-staging/hadoop_hive_2016-12-16_13-06-26_309_6781206579090170211-1/-ext-10000
Loading data to table test.query_result
Table test.query_result stats: [numFiles=1, numRows=3, totalSize=69, rawDataSize=66]
MapReduce Jobs Launched:
Stage-Stage-1: Map: 2 Reduce: 1 Cumulative CPU: 17.7 sec HDFS Read: 13657 HDFS Write: 142 SUCCESS
Total MapReduce CPU Time Spent: 17 seconds 700 msec
OK
owntest.name owntest.age owntest2.workplace owntest2.name
Time taken: 96.932 seconds
以上是在客户端、JobTracker、TaskTracker的层次来分析MapReduce的工作原理的,下面我们再细致一点,从map任务和reduce任务的层次来分析分析吧。
也就分成input,split,map,shuffle,reduce五个步骤,这里我们按照例子来简单说明下,再详细的说明我会放在后面的细分部分,这部分作用是了解下mapreduce的怎么个流程。这里是一个wordcount的过程了
input:也就是数据存储位置,这里当然是类似于hdfs这样的分布式存储啦,
split:因为map task只读split,而split基本上和hdfs的基本存储块block同样大小注意:split是逻辑分片,一个split对应一个map,你可以把它当做map的单位块来理解,投喂进map的时候必须要这样的格式,打个比方,比如只收硬币的地铁站,你只能投放1元硬币的,你投什么五毛,一角的,都是犯法的!对,警察叔叔就是他!
map:这里做的是wordcount,而map程序是由程序原来编写的,如果非要用代码来写,我用python写你别打我
splitdata = 'Deer Bear River'
aftermap = map(lambda x:(x,1),splitdata.split(" "))
print aftermap
# [('Deer', 1), ('Bear', 1), ('River', 1)]
三个map对应三组Split,我这里只取了其中一组,就是这么个意思,组成key/value键值对
shuffle:这是一个比较核心的过程,shuffle有洗牌的意思,这里的意思你把她理解成在拉斯维加斯赌场发牌的小姐姐,但是这个小姐姐并不是随机发牌,而是把红桃发给A先生,黑桃都发给B先生,诸如此类。如果非要说有什么套路,那么其中有一个HashPartitioner就帮我们做了hash,你把它想成低配版的聚类,狭义版聚类,因为这里的类特喵的必须是值hash(key)%reduceNum相等的发到同一个reduce里去 啊!
reduce:既然都说了似wordcount了,那我,额,额,做戏做全套,我还是用python来写这个过程
shuffledata = [('Deer', 1),('Deer', 1),('River', 1)]
afterreduce_dict = {}
for i in shuffledata:
if i[0] not in afterreduce_dict:
afterreduce_dict[i[0]] = 0
afterreduce_dict[i[0]] +=1
print afterreduce_dict
# {'River': 1, 'Deer': 2}
这个‘reduce’只是对传统意义上的取键值对统计而已,理解这层意思就行,不要纠结形式
总体的步骤就是这么5步,其实最玄妙的shuffle还没有拆开细讲,接下来,见证shuffle的时刻,百分之九十抄的(捂脸)
除了map,reduce,中间部分就是shuffle,它横跨了map和reduce两端,可以说是核心过程
这里你只要清楚Shuffle的大致范围就成-怎样把map task的输出结果有效地传送到reduce端。也可以这样理解, Shuffle描述着数据从map task输出到reduce task输入的这段过程。你说我map task干完活了要输出数据了,然后接下来数据给哪个reduce?不能因为我和那个reduce关系好,我把所有输出结果都给它把,总要考虑下别的reduce的感受啊!那怎么分配?就有了shuffle过程!上面给了两张图,我都是盗来的,一张是官方的解释,一张是中文翻译,Tkanks-MapReduce:详解Shuffle过程、MapReduce工作原理图文详解
在Hadoop这样的集群环境中,大部分map task与reduce task的执行是在不同的节点上。当然很多情况下Reduce执行时需要跨节点去拉取其它节点上的map task结果。如果集群正在运行的job有很多,那么task的正常执行对集群内部的网络资源消耗会很严重。这种网络消耗是正常的,我们不能限制,能做的就是最大化地减少不必要的消耗。还有在节点内,相比于内存,磁盘IO对job完成时间的影响也是可观的。从最基本的要求来说,我们对Shuffle过程的期望可以有:
与上图步骤1,2,3,4一一对应来解释
Running map task 就是程序员编写好的map函数了,因此map函数效率相对好控制,而且一般map操作都是本地化操作也就是在数据存储节点上进行,这也就是上问所提到的 数据本地化!
**Memory Buffer & 3:**这是一个环形的缓冲区。map task输出结果首先会进入一个缓冲区内,这个缓冲区的大小是100MB,如果map task内容太大,是很容易撑爆内存的,所以会有一个守护进程,每当缓冲区到达上限80%的时候,就会启动一个Spill(溢写)进程,它的作用是把内存里的map task的结果写入到磁盘。这里值得注意的是,溢写程序是单独的一个进程,不会影响map task的继续输出(在写磁盘过程中,另外的20%内存可以继续写入数据,两种操作互不干扰,但如果在此期间缓冲区被填满,那么map就会阻塞写入内存的操作,让写入磁盘操作完成后再执行写入内存。)。当溢写线程启动后,需要对这80MB空间内的key做排序(Sort)。排序是MapReduce模型默认的行为,这里的排序也是对序列化的字节做的排序。shuffle排序可以参考这里
Combiner:如果client设置过Combiner,那么现在就是使用Combiner的时候了。将有相同key的key/value对的value加起来,减少溢写到磁盘的数据量,combiner简单说就是map端的reduce!。Combiner会优化MapReduce的中间结果,所以它在整个模型中会多次使用。那哪些场景才能使用Combiner呢?从这里分析,Combiner的输出是Reducer的输入,Combiner绝不能改变最终的计算结果。所以从我的想法来看,Combiner只应该用于那种Reduce的输入key/value与输出key/value类型完全一致,且不影响最终结果的场景。比如累加,最大值等。但对于均值就会有影响;Combiner的使用一定得慎重,如果用好,它对job执行效率有帮助,反之会影响reduce的最终结果。
引用:然后为merge过程创建一个叫file.out的文件和一个叫file.out.Index的文件用来存储最终的输出和索引,一个partition一个partition的进行合并输出。对于某个partition来说,从索引列表中查询这个partition对应的所有索引信息,每个对应一个段插入到段列表中。也就是这个partition对应一个段列表,记录所有的Spill文件中对应的这个partition那段数据的文件名、起始位置、长度等等。然后对这个partition对应的所有的segment进行合并,目标是合并成一个segment。当这个partition对应很多个segment时,会分批地进行合并:先从segment列表中把第一批取出来,以key为关键字放置成最小堆,然后从最小堆中每次取出最小的输出到一个临时文件中,这样就把这一批段合并成一个临时的段,把它加回到segment列表中;再从segment列表中把第二批取出来合并输出到一个临时segment,把其加入到列表中;这样往复执行,直到剩下的段是一批,输出到最终的文件中。最终的索引数据仍然输出到Index文件中。
也就是说,上述三个spill.out文件中,同一个颜色的表示计算出的partition的值是一致的,根据每一个spill.out文件都有spill.index文件对应,里面记录了该out文件中某个partition的位置,将同一个partition从三个spill.out文件中都拿出来进行归并操作,搞定后变成了最终文件的头部,然后再采集蓝色部分代表的partition,往复操作,最终每个map都会对应出一个结果文件,等待reduce来拉取自己需要处理的部分,至于怎么拉,拉谁,这个都记录在index中,其实就是partition的值了
在spill的过程中时,会对partition和key进行排序,确保partition内的key是有序的,这里用的是优化后的快排;然后再溢写到小文件之后,针对这些小文件,最终需要merge到一个文件,这里也会出现一次排序,这里用的基本思想就是归并排序,其实是可以理解的,因为在溢写的文件块内,已经保证局部有序了,相当于只需要将局部有序的列表进行合并,形成一个全局有序的,这个让归并来做实在合适,甚至只需要做归并的后半部分!我用代码阐述下
# 假设两个溢写文件且是两个partition的情况,里面的key已经有序了,现在需要全局有序,也就是小文件a,b merge到temp的过程
a = [1,2,4,7,20,23]
b = [3,5,9,11,13,17,19,20]
a_size = len(a)
b_size = len(b)
temp = []
i,j = 0,0
while i != a_size and j != b_size:
if a[i] < b[j]:
temp.append(a[i])
i = i + 1
else:
temp.append(b[j])
j = j + 1
temp.extend(b[j:] if i == a_size else a[i:])
[1, 2, 3, 4, 5, 7, 9, 11, 13, 17, 19, 20, 20, 23]
代码比较简陋,但是阐述了归并的基本思想,就是使用外部排序的方式,开辟一个缓冲区进行排序,这样就不会局限于内存的限制了,其实在大数据排序中经常碰到,指针交替前进,最后刷完整个列表;
至此,map端的所有工作都已结束,最终生成的这个文件也存放在TaskTracker够得着的某个本地目录内。每个reduce task不断地通过RPC从JobTracker那里获取map task是否完成的信息,如果reduce task得到通知,获知某台TaskTracker上的map task执行完成,Shuffle的后半段过程开始启动。
关于这个过程更详细的请见 MapReduce shuffle过程详解
@上述所有链接