【MapReduce篇】MR过程分析

Mapreduce原语:“相同”的key为一组,调用一次reduce方法,方法内迭代这一组数据进行计算
为什么叫MapReduce:MapTask & ReduceTask
整体运行流程图解:
【MapReduce篇】MR过程分析_第1张图片
mapreduce 框架可以分为五个不同实体:

1)客户端:提交 MapReduce job。

2)Yarn 资源管理器(resource manager):协调集群计算资源的分配

3)Yarn 节点管理器(node manager):启动和监视集群中每个节点的计算容器。

4)Mapreduce 应用管理器(application master):负责调度 mapreduce 任务。应用管理器和 MapReduce 任务是运行在容器中的,这个容器是由资源管理器分配的,并且接受阶段管理器的管理。

5) 分布式文件系统(通常为HDFS):用与上述不同实体之间文件的共享。

【MapReduce篇】MR过程分析_第2张图片

几部分的关系:

block > split
1:1
N:1
1:N
split > map
1:1
map > reduce
N:1
N:N
1:1
1:N
group(key)>partition
1:1
N:1
N:N
1:N?  > 违背了原语
partition > outputfile

1.shuffle
Shuffler<洗牌>:框架内部实现机制
分布式计算节点数据流转:连接MapTask与ReduceTask

Shuffle过程是MapReduce的核心,也被称为奇迹发生的地方。要想理解MapReduce, Shuffle是必须要了解的

两种流派:1.reduce端拷贝数据。3.从reduce输出一个记录算出分区号开始,算shuffle,自然就包括了排序合并文件以及拷贝,到reduce计算之前的所有操作。
【MapReduce篇】MR过程分析_第3张图片
在Hadoop这样的集群环境中,大部分map task与reduce task的执行是在不同的节点上。当然很多情况下Reduce执行时需要跨节点去拉取其它节点上的map task结果。如果集群正在运行的job有很多,那么task的正常执行对集群内部的网络资源消耗会很严重。这种网络消耗是正常的,我们不能限制,能做的就是最大化地减少不必要的消耗。还有在节点内,相比于内存,磁盘IO对job完成时间的影响也是可观的。从最基本的要求来说,我们对Shuffle过程的 期望可以有:

1.完整地从map task端拉取数据到reduce 端。
2.在跨节点拉取数据时,尽可能地减少对带宽的不必要消耗。
3.减少磁盘IO对task执行的影响。

上述的官网的图解并不是非常的详细,下以WordCount为例,并假设它有8个 map task和3个reduce task下面细化其过程:
【MapReduce篇】MR过程分析_第4张图片
图片源
分析:
整个流程分了四步。简单些可以这样说,每个map task都有一个内存缓冲区,存储着map的输出结果,当缓冲区快满的时候需要将缓冲区的数据以一个临时文件的方式存放到磁盘,当整个map task结束后再对磁盘中这个map task产生的所有临时文件做合并,生成最终的正式输出文件,然后等待reduce task来拉数据。 这也是官网的map部分。

当然这里的每一步都可能包含着多个步骤与细节,下面对细节具体说明:

  1. 在map task执行时,它的输入数据来源于HDFS的block,当然在MapReduce概念中,map task只读取split。Split与block的对应关系可能是多对一,默认是一对一。在WordCount例子里,假设map的输入数据都是像 “aaa”这样的字符串。

  2. 在经过mapper的运行后,我们得知mapper的输出是这样一个key/value对: key是“aaa”, value是数值1。因为当前map端只做加1的操作,在reduce task里才去合并结果集。前面我们知道这个job有3个reduce task,到底当前的“aaa”应该交由哪个reduce去做呢,是需要现在决定的。
    Partitioner引入: MapReduce提供Partitioner接口,它的作用就是根据key或value及reduce的数量来决定当前的这对输出数据最终应该交由哪个 reduce task处理。默认对key进行hash后再和reduce task数量取模。默认的取模方式只是为了平均reduce的处理能力,如果用户自己对Partitioner有需求,可以订制并设置到job上。
    在例子中,“aaa”经过Partitioner后返回0,也就是这对值应当交由第一个reducer来处理。接下来,需要将数据写入内存缓冲区 中,缓冲区的作用是批量收集map结果,减少磁盘IO的影响。我们的key/value对以及Partition的结果都会被写入缓冲区。当然写入之 前,key与value值都会被序列化成字节数组。

  3. spill和combiner:这个内存缓冲区是有大小限制的,默认是100MB。当map task的输出结果很多时,就可能会撑爆内存,所以需要在一定条件下将缓冲区中的数据临时写入磁盘,然后重新利用这块缓冲区。这个从内存往磁盘写数据的过程被称为Spill,中文可译为溢写,字面意思很直观。这个溢写是由单独线程来完成,不影响往缓冲区写map结果的线程。溢写线程启动时不应该阻止map 的结果输出,所以整个缓冲区有个溢写的比例spill.percent。这个比例默认是0.8,也就是当缓冲区的数据已经达到阈值(buffer size * spill percent = 100MB * 0.8 = 80MB),溢写线程启动,锁定这80MB的内存,执行溢写过程。Map task的输出结果还可以往剩下的20MB内存中写,互不影响。

    当溢写线程启动后,需要对这80MB空间内的key做排序(Sort)。排序是MapReduce模型默认的行为,这里的排序也是对序列化的字节做的排序。

    map task的输出是需要发送到不同的reduce端去,而内存缓冲区没有对将发送到相同reduce端的数据做合并,那么这种合并应该是体现是磁盘文件中 的。从官方图上也可以看到写到磁盘中的溢写文件是对不同的reduce端的数值做过合并。所以溢写过程一个很重要的细节在于,如果有很多个 key/value对需要发送到某个reduce端去,那么需要将这些key/value值拼接到一块,减少与partition相关的索引记录。

    在针对每个reduce端而合并数据时,有些数据可能像这样:“aaa”/1, “aaa”/1。对于WordCount例子,就是简单地统计单词出现的次数,如果在同一个map task的结果中有很多个像“aaa”一样出现多次的key,我们就应该把它们的值合并到一块,这个过程叫reduce也叫combine。但 MapReduce的术语中,reduce只指reduce端执行从多个map task取数据做计算的过程。除reduce外,非正式地合并数据只能算做combine了。其实大家知道的,MapReduce中将Combiner等 同于Reducer。

    使用combiner注意事项:如果client设置过Combiner,那么现在就是使用Combiner的时候了。将有相同key的key/value对的value加起来,减少溢 写到磁盘的数据量。Combiner会优化MapReduce的中间结果,所以它在整个模型中会多次使用。那哪些场景才能使用Combiner呢?从这里 分析,Combiner的输出是Reducer的输入,Combiner绝不能改变最终的计算结果。所以从我的想法来看,Combiner只应该用于那种 Reduce的输入key/value与输出key/value类型完全一致,且不影响最终结果的场景。比如累加,最大值等。Combiner的使用一定 得慎重,如果用好,它对job执行效率有帮助,反之会影响reduce的最终结果。

  4. Merge过程:每次溢写会在磁盘上生成一个溢写文件,如果map的输出结果真的很大,有多次这样的溢写发生,磁盘上相应的就会有多个溢写文件存在。当map task真正完成时,最终磁盘中会至少有一个这样的溢写文件存在(如果map的输出结果很少,当 map执行完成时,只会产生一个溢写文件),因为最终的文件只有一个,所以需要将这些溢写文件归并到一起,这个过程就叫做Merge。Merge是怎样 的?如前面的例子,“aaa”从某个map task读取过来时值是5,从另外一个map 读取时值是8,因为它们有相同的key,所以得merge成group。什么是group。对于“aaa”就是像这样的:{“aaa”, [5, 8, 2, …]},数组中的值就是从不同溢写文件中读取出来的,然后再把这些值加起来。请注意,因为merge是将多个溢写文件合并到一个文件,所以可能也有相同的 key存在,在这个过程中如果client设置过Combiner,也会使用Combiner来合并相同的key。

    至此,map端的所有工作都已结束,最终生成的这个文件也存放在TaskTracker够得着的某个本地目录内。对于hadoop1.x而言,每个reduce task不断地通过RPC从JobTracker那里获取map task是否完成的信息,如果reduce task得到通知,获知某台TaskTracker上的map task执行完成,Shuffle的后半段过程开始启动。

    总结: reduce task在执行之前的工作就是不断地拉取当前job里每个map task的最终结果,然后对从不同地方拉取过来的数据不断地做merge,也最终形成一个文件作为reduce task的输入文件。

【MapReduce篇】MR过程分析_第5张图片

如map 端的细节图,Shuffle在reduce端的过程也能用图上标明的三点来概括。当前reduce copy数据的前提是它要从JobTracker获得有哪些map task已执行结束,Reducer真正运行之前,所有的时间都是在拉取数据,做merge,且不断重复地在做。 如前面的方式一样,下面也分段地描述reduce 端的Shuffle细节:

  1. Copy过程:简单地拉取数据。Reduce进程启动一些数据copy线程(Fetcher),通过HTTP方式请求map task所在的TaskTracker获取map task的输出文件。因为map task早已结束,这些文件就归TaskTracker管理在本地磁盘中。

  2. Merge阶段:这里的merge如map端的merge动作,只是数组中存放的是不同map端copy来的数值。Copy过来的数据会先放入内存缓冲区 中,这里的缓冲区大小要比map端的更为灵活,它基于JVM的heap size设置,因为Shuffle阶段Reducer不运行,所以应该把绝大部分的内存都给Shuffle用。这里需要强调的是,merge有三种形 式:1)内存到内存 2)内存到磁盘 3)磁盘到磁盘。默认情况下第一种形式不启用,让人比较困惑,是吧。当内存中的数据量到达一定阈值,就启动内存到磁盘的merge。与map 端类似,这也是溢写的过程,这个过程中如果你设置有Combiner,也是会启用的,然后在磁盘中生成了众多的溢写文件。第二种merge方式一直在运行,直到没有map端的数据时才结束,然后启动第三种磁盘到磁盘的merge方式生成最终的那个文件。

  3. Reducer的输入文件。不断地merge后,最后会生成一个“最终文件”。为什么加引号?因为这个文件可能存在于磁盘上,也可能存在于内存中。对我们 来说,当然希望它存放于内存中,直接作为Reducer的输入,但默认情况下,这个文件是存放于磁盘中的。至于怎样才能让这个文件出现在内存中,参见性能优化篇。当Reducer的输入文件已定,整个Shuffle才最终结束。然后就是Reducer执行,把结果放到HDFS上。

2.map端分析
此阶段主要有,把不同的split切片通过对应个数的map方法,生成小文件,在生成小文件的过程中产生分区、排序、归并的过程。每个小文件内部有序,之间可能无序。然后通过shuffle的过程拉取。后由reduce端处理。

3.reduce端分析
sort: reuduce端此排序只是归并,没有产生复杂的排序,不能跟map端一样做了复杂的排序。而且reduce端不能更改由map端已经排序好的顺序。只是在数据不变的情况下做了个归并。没有重排或更改数据。

备注:
mapper的个数由切片个数决定的。reduce的个数是由人来决定的。默认是一个。然后自定义,如果多个可并行跑mapper中的不同的key。注意数据倾斜的情况,

使用mapreduce自带的jar跑wordcount案例。
在hadoop的安装目录下,到/share/hadoop/mapreduce 下找到hadoop-mapreduce-examples-2.6.5.jar 后执行测试文件。
在hdfs上找到上传过的一个文件。

hadoop jar hadoop-mapreduce-examples-2.6.5.jar wordcount  /user/root/ccc.txt  /wc/out

查看结果:

执行的过程可以在网页端查看进度。node03:8088/
查看执行完成后的情况,在hdfs上查看/wc/out下的文件即可。或者有命令行操作,
hdfs dfs -cat /wc/out/接具体的文件名称比如: part-r-00000

MR运行方式:
MapReduce运行 3 种方式:

一、本地方式运行:
1、pc环境:
    1.1、将Hadoop安装本地解压
    1.2、配置Hadoop的环境变量
        添加%HADOOP_HOME%
        修改%PATH%  添加%HADOOP_HOME%/bin;%HADOOP_HOME%/sbin
    1.3、在解压的Hadoop的bin目录下 添加winutils.exe工具
2、Java工程
    2.1、jdk一定要使用自己的jdk、不要使用eclipse自带
    2.2、根目录(src目录下),不要添加任何MapReduce的配置文件  hdfs-site.xml yarn-site.xml core-site.xml mapred-site.xml
    2.3、在代码当中,通过conf.set方式来进行指定。conf.set("fs.defaultFS", "hdfs://node1:8020");
    2.4、修改Hadoop源码
3、右键run执行

集群运行两种方式:
二、
    Java工程
    1、根目录(src目录下),添加Hadoop的配置文件 hdfs-site.xml yarn-site.xml core-site.xml mapred-site.xml
    2、在代码当中,指定jar包的位置,config.set("mapred.jar", "D:\\MR\\wc.jar");
    3、修改Hadoop源码
    4、将工程打jar包
    5、右键run执行

三、
    Java工程
    1、根目录(src目录下),添加Hadoop的配置文件 hdfs-site.xml  yarn-site.xml core-site.xml mapred-site.xml
    2、将工程打jar包
    3、手动将jar包上传到集群当中
    4、通过hadoop命令来运行。hadoop jar jar位置 mr代码入口 (例如:hadoop jar /usr/wc.jar com.sxt.mr.WcJob)


HBase运行:
在代码当中指定HBase所使用的ZooKeeper集群。
(注意:如果hbase搭建的是伪分布式,那么对应的ZooKeeper就是那台伪分布式的服务器)
conf.set("hbase.zookeeper.quorum", "node1,node2,node3");
System.setProperty("HADOOP_USER_NAME", "root");

wordCount案例分析:
客户端:MyWc.java

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

public class MyWC {

    public static void main(String[] args) throws Exception {
        // 1.加载配置
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf);
        job.setJarByClass(MyWC.class);
        job.setJobName("TcwcJob");
        // 设置输入路径
        Path input = new Path("/user/root");
        FileInputFormat.addInputPath(job, input);
        // 设置输出路径
        Path out = new Path("/wc/TcOutput");
        // 判断输出路径如果有则删除
        if (out.getFileSystem(conf).exists(out)) {
            out.getFileSystem(conf).delete(out, true);
        }
        FileOutputFormat.setOutputPath(job, out);
        // 设置map端
        job.setMapperClass(MyMapper.class);
        job.setMapOutputKeyClass(Text.class); // 注此处导包要导hadoop的io的包。其他的包就会报:Unable to initialize any output collector 
        job.setMapOutputValueClass(IntWritable.class);
        job.setReducerClass(MyReduce.class);
        job.waitForCompletion(true);
    }
}

map端:MyMapper.java

import java.io.IOException;
import java.util.StringTokenizer;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

public class MyMapper extends Mapper<Object, Text, Text, IntWritable> {
    private static IntWritable one = new IntWritable(1);
    private Text word = new Text();
    // 重写map方法
    // 具体的操作:将每一行的字符串按特定的标记分割成单个单词然后对单词进行标记,单词作为key。次数作为value。到reduce端,相同的key为一组,计算value的个数
    @Override
    protected void map(Object key, Text value, Context context) throws IOException, InterruptedException {
        // 拿到每行的数据然后遍历
        StringTokenizer itr = new StringTokenizer(value.toString());
        while (itr.hasMoreTokens()) {
            word.set(itr.nextToken());
            context.write(word, one);
        }
    }
}

reduce端:MyReduce.java

import java.io.IOException;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

public class MyReduce extends Reducer<Text, IntWritable, Text, IntWritable> {
    private IntWritable result = new IntWritable();
    protected void reduce(Text key, Iterable values, Context context)
            throws IOException, InterruptedException {
        int sum = 0;
        for (IntWritable val : values) {
            sum += val.get();
        }
        result.set(sum);
        context.write(key, result);
    }
}

测试:
导出jar后运行,如下:
hadoop jar MyWC.jar 主方法对应的类路径

//TODO: mapreduce源码分析注意事项:

你可能感兴趣的:(【MapReduce篇】MR过程分析)