Yarn及MapReduce工作流程(总结版)
(未写出在hdfs上传和下载数据的流程,此流程请详见nn和2nn的爱恨情仇以及nn和dn的老大和小弟的故事)
Yarn工作流程:
1.MR程序提交到客户端所在的节点,客户端向RM发出申请Application的请求
2.RM同意,并返回提交资源的路径stag和application_id。
3客户端向集群的hdfs提交job运行所需要的资源,提交之后会生成向集群提交数据的stag路径、生成job_id、FileInputFormat调用getSplites()获取切片规划,并序列化成二进制文件、含有job运行参数的job.xml、集群运行所需要的jar包。
4.资源提交完毕之后,向RM提交执行MRAppMaster的请求。
5.RM把客户端的请求变成Task,并放进容量资源调度器(hadoop默认的),等待节点认领。
6.某个节点拿到Task之后,生成Contain容器,并运行MRAppmaster。
7.下载hdfs上的资源到本地。
8.根据在hdfs上下载的切片规划,MR向RM申请运行运行的切片等同数量的MapTask。
9.RM把Task放容量资源调度器,等待指定的节点领取MapTask。
10.节点领取到MapTask之后,生成contain,并执行MapTask;
MapTask阶段:
11.MapTask会调用默认的TextInputFormat使用LineRecordReader一行行读取文件的数据。(read阶段)
12.读取的数据经过map()之后,会被上下文对象context以kv键值对的形式写出会,并等待被Collector环形缓冲区(默认是100m)使用collect()收集。(map阶段)
13.数据到了环形缓冲区之后,会选择一个分界点,kv数据会向右边存储,kv的元信息(包括索引、分区信息、keystart、valuestart)向左边存储,当达到环形缓冲区的百分之八十之后(默认是半分之八十),在剩下的20m中寻找一个新的分界点,存储的形式跟原来保持一致;存储完毕的80m会溢出落盘,在溢出之前会进行一次快速排序,保证数据在区内有序,如果有conbiner(),那么会进行区内的key合并,做完这些之后才进行落盘。如果写入环形缓冲区的速度大于溢出的速度,写入会等待一下。(collect阶段、spill(溢出)阶段)
14.溢出完毕之后,会把本节点所有的溢出文件进行一次归并排序形成一个大的文件,如果有conbiner(),会进行一次节点内合并key。(meger(合并)阶段)
RedcueTask阶段:
15.ReduceTask会主动copy自己负责的区的所有节点的数据,如果数据不大,会全部放在内存中,如果数据很大会溢写到磁盘里。(ReduceTask的数量最好跟分区数保持一致,如果分区数大于1,RedcueTask的数目小于分区数会报Exception,如果大于,多于的部分会生成空文件,如果就有一个ReduceTask那么就只会生成一个文件)。(copy阶段)
16.ReduceTask会把数据进行归并排序。(meger和sort阶段)
17.经过归并排序之后的数据计入reduce方法,之后数据会通过TextOutputFormat(hadoop默认的输出)的RecordWriter写到hdfs上面。OutputFormat可以自定义,详细内容如下(rduce阶段)。
OutputFormat阶段:
18.可以选择自定义输出(其实就是按照需求,把数据存到存到的路径),具体的实现就是自定义类继承OutputFormat,覆写里面的getRecordWriter(),要求返回RecordWriter对象,所以又要自定义一个类继承RecordWirter类并覆写里面的write()和close(),其实就是需要输出流,根据需求输出到不同的位置即可,一下是具体的代码:
MyOutputFormat类:
public class MyOutputFormat extends FileOutputFormat<Text, NullWritable> {
@Override
public RecordWriter<Text, NullWritable> getRecordWriter(TaskAttemptContext job) throws IOException, InterruptedException {
LogRecordWriter logRecordWriter = new LogRecordWriter(job);
return logRecordWriter;
}
}
LogRecordWriter类:
public class LogRecordWriter extends RecordWriter<Text, NullWritable> {
FileSystem fs = null;
FSDataOutputStream out1 = null;
FSDataOutputStream out2 = null;
public LogRecordWriter(TaskAttemptContext job) throws IOException {
//利用job对象获取配置文件对象
Configuration configuration = job.getConfiguration();
//获取FileSystem对象
fs = FileSystem.get(configuration);
//获取输出流
out1 = fs.create(new Path("D://temp/log1"));
out2 = fs.create(new Path("D://temp/log2"));
}
@Override
public void write(Text key, NullWritable value) throws IOException, InterruptedException {
if(key.toString().contains("atguigu")){
out1.writeBytes(key.toString()+"\n");
}else {
out2.writeBytes(key.toString()+"\n");
}
}
@Override
public void close(TaskAttemptContext context) throws IOException, InterruptedException {
//使用IO工具类IOUtils关闭流
IOUtils.closeStream(out1);
IOUtils.closeStream(out2);
}
}