一.知识点
1.wc.jar这个包里包括
1)AppMaster:用于阶段调度,它负责程序中各个阶段的调度,一个jar包一个AppMaster。
阶段一:MapTask
阶段二:ReduceTask
2.job
用户的每一个计算请求称为一个作业.
每个作业包括:
1)文件的大小
2)一个片的大小
3)任务的数量
4)Job的名字“wordcount”
5)AppMaster分配的资源等
3.Task
每一个作业都需要被拆分开来,交由多个主机来完成,拆分出来的执行单位就是任务.
Task又分为如下三种类型的任务:
1)MapTask
负责Map阶段数据的处理
MapTask调用map()方法,结果返回给MapTask
MapTask的并行度:开启多少个MapTask
MapTask个数的决定因素:
1)数据量
2)一个片的大小
3)文件的个数
2)ReduceTask
负责整个Reduce阶段数据的处理
3)MRAppMaster
负责整个程序的过程调度及状态调优
4.RunJar
提交Job进程.
5.ResourceManager
ResourceManager只是负责分配资源.AppMaster申请多少资源,ResourceManager就分配多少资源,不管分配的资源是否被利用,ResourceManger不做任何交验。
6.Container
在NodeManager在Container上运行程序,Container是一块资源的容器。
Container是Yarn中的一个动态资源分配的概念。由ResouceManger分配给AppMaster或者MapTask或者ReduceTask。
7.申请多少资源由谁决定
由客户端决定。申请多少资源就是申请Container,Container是分配给
1)AppMaster(只要有MapReduce程序就有一个)
2)MapTask(客户端提交job信息中有文件的大小,一个片多大,文件的个数,就可以算出有多少片,就有多少个MapTask)
3)ReduceTask(由配置文件决定,写到job里)
job.setNumReduceTasks(3); //设置reducetask的个数,默认是1
8.移动计算而不移动数据
数据存储在HDFS上,计算在NodeManger的Continer上,所以NodeManger和DataNode是不分离的。
9.Yarn
Yarn集群负责公司所有job的运行,所以有可能提交job的时候,没有运行资源可以使用(全部占满),其实yarn有一个等待队列,把job放在里面。
二.MapReduce Job提交流程
1客户端向ResorceManager提交job
2ResorceManager响应,返回jobId,返回jar包在HDFS上的路径
3.客户端把wc.jar放到HDFS上
4.ResourceManager分配一个Container资源给AppMaster
5.客户端给Container发启动AppMaster的命令
6.AppMaster向ResourceManager申请MapTask的资源,AppMaster申请多少资源,ResourceManager就分配多少资源,不管分配的资源是否被利用,ResourceManager不做任何校验.
7.ResourceManager为MapTask分配资源后,把任务的信息(jar的存放路径,有哪些机器运行,每台机器上运行的数据是什么)放到队列里.
8.NodeManager每隔1秒向ResourceManager发送一次心跳,ResourceManger把任务的信息返回给它,当任务的信息中指定的机器有它的话,当它空闲的时候就会到HDFS上把jar包拉取下来,并给AppMaster一个响应,告知已经拿到jar包,询问AppMaster怎么启动任务.拿到AppMaster的响应后,启动MapTask.
9.AppMaster监控MapTask的状态,当MapTask运行结束的时候,AppMaster会接到它的反馈,会继续申请资源运行ReduceTask.只要有一个MapTask结束,就可以启动ReduceTask,ReduceTask收集MapTask运行完的数据,当所有MapTask运行完了,数据也就收集完了,就可以运行汇总程序.
三.MapReduce全流程
1.TextInputFormat.createRecordReader(),返回的是LineRecordReader类的对象(把文本切成行).
2.生成的LineRecordReader类的对象,可以使用方法:
nextKeyValue()返回值为boolean类型
getCurrentKey()返回k(偏移量)
getCurrentValue()返回v(该行的内容)
3.将得到的k,v传给mapTask
4.mapTask调用自己的MyMapper类的map(k1,v1,context)方法.context.write(k2,v2)将返回值写往MapTask,这个写过程:
MapTask新开一个线程:OutputCollection(收集数据,将数据放进环形缓冲器).这个环形缓冲器默认大小为100M,环形缓冲器的数据按照发出顺序接收.当80M满了.MapTask会新开一个线程:Spiller溢出器,它负责环形缓冲器的溢出.要求这个溢出的过程能快速进行(所以用内存),溢出做了三件事:
1)分区:hashPartitioner
2)排序:用快速排序
3)溢出:将数据写往磁盘
5.图解溢出
数据一直写,当完成80%,在溢出的同时,把指针指到保留区开始写,溢出完,将环上空白区域设成新的保留区,原来的保留区因为有了数据,直接变成80%的一部分.
问题:
1)万一20M不够怎么办?
20M是默认值,是通过大量测试得到的一个通用值,这个值可以通过配置文件修改.
2)最后一次环形缓冲器没有写满没办法溢出怎么办?
MapTask读完数据后,环形缓冲区是它的一个进程,MapTask会检查环形缓冲器中是否还有内容,如果有则溢出,然后才结束.
3)怎么知道结果的数据属于哪个区?
有一个维护分区的东西:index
6.在一个Map Task结束后
0号分区发往0号ReduceTask
1号分区发往1号ReduceTask
2号分区发往2号ReduceTask
有一个MapTask端结束,就可以开始ReduceTask,ReduceTask是AppMaster启动的.ReduceTask启动之后先拉取相应分区的数据.
等所有MapTask端输出的文件都在ReduceTask合并完,ReduceTask调用MyReducer的reduce(👁,v2,context).
数据是怎么传入这个方法的?
该方法从文本文件中去读数据,key为k2,value为迭代器,迭代器调用
while(is.hasNext())
v=it.next()
处理.
注:ReduceTask没有分区的概念,因为一个ReduceTask它的内容就是属于它的分区的内容.map端的输出是reduce端的输入,在调用reduce()方法时要做:
1)ReduceTask先从合并后的文件中读取一个key传入reduce()方法,同时传入一个迭代器.
2)迭代器的hasNext()方法会判断文件的下一个key是否还是它传入reduce()方法的key,如果是,next可以返回一个value,否则hasNext()直接返回false,导致本次reduce方法的调用结束.重新开始读下一个key.
3)看起来的效果就是:ReduceTask会将数据按照key事先分好组,然后对每一组数据调用一次reduce方法.
7. ReduceTask调用context.write(k3,v3)结果写到哪里呢?
ReduceTask调用TextOutputFormat的createRecordWriter()方法,该方法返回一个LineRecordWriter类的对象.调用该对象的writelines()直接按行写,写入外部文件系统.
FileOutputFormat.setOutputPath(job,new Path("hdfs://namenode1:9000/aaa/output"))
8.shiffle洗牌在哪?
从map端的输出到reduce端的输入.
9.WordCount中分片是否能把单词切分成一半?为什么不会发生这样的情况?
若文件为300M,则分3片(默认以块的大小分片,128M一块)
文件先分片,分片后的还是文件(2进制)
注:分片是逻辑上的.
规则:往下多读一行,下一片抛弃第一行.
特殊情况:文件的起始片不抛弃第一行,最后一片不往后多读一行.
怎么读?
in.readLine()
碰到换行(\r\n)就结束,不管读的是哪一片,在处理时就是把整个文件打开进行处理的,分片只是存储的概念(逻辑上分片,物理上分块)。
怎么结束这个片的读取?
读取数据>要处理的数据长度(比较偏移量,若大于等于则结束,把下一片的第一行读了,下一个文件第一行数据放弃).