MapReduce的工作原理(一)

1. 前言

Hadoop中有两个最重要的组件,一个是HDFS,另一个是MapReduce,HDFS用来存储大量数据,而MapReduce则是通过计算来发现数据中有价值的内容。关于MapReduce的介绍我想通过如下几个方面分步讲解:

  1. MapReduce的工作原理
  2. MapReduce的实例
  3. MapReduce进阶编程

2. MapReduce工作流程

在介绍MapReduce的工作流程时,先介绍一下MapReduce的四个对象:

  1. 客户端

编写mapreduce程序,配置任务,提交任务。

  1. JobTracker

初始化作业,分配作业,与TaskTracker通信,协调整个作业的执行。

  1. TaskTracker

保持与JobTracker的通信,在分配的数据片段上执行Map或Reduce任务,TaskTracker和JobTracker的不同有个很重要的方面,就是在执行任务的时候TaskTracker可以有很多个,而JobTracker只会有一个。

  1. HDFS

保存作业的数据、配置信息等,最后的结果也保存在HDFS上面。

下面给出MapReduce的工作流程图:
在这里插入图片描述
下面尽量以通俗的语言来讲解MapReduce的工作流程:

  1. 首先是客户端要编写好MapReduce程序,配置好MapReduce的作业也就是Job,接下来就要提交Job,提交Job是提交到JobTracker上的,这个时候JobTracker就会构建这个Job,具体就是分配一个新的Job任务的ID值。
  2. 接下来它会做检查操作,这个检查就是确定输出目录是否存在,如果存在那么Job就不能正常运行,JobTracker会抛出错误给客户端,接下来还要检查输入目录是否存在,如果不存在同样抛出错误,如果存在JobTracker会根据输入计算输入计算输入分片(Input Split),如果分片计算不出来也会抛出错误。当这些都做好了JobTracker就会配置Job需要的资源了。
  3. 分配好资源后,JobTracker就会初始化作业,初始化主要做的是将Job放入一个内部的队列,让配置好的作业调度器能调度到这个作业,作业调度器会初始化这个Job,初始化就是创建一个正在运行的Job对象,以便JobTracker跟踪Job的状态和进程。
  4. 初始化完毕后,作业调度器会获取输入分片信息(input split),每个分片创建一个map任务。
  5. 接下来就是任务分配了,这个时候TaskTracker会运行一个简单的循环机制定期发送心跳给JobTracker,心跳间隔是5秒,程序员可以配置这个时间,心跳就是JobTracker和TaskTracker沟通的桥梁,通过心跳,Jobtracker可以监控TaskTracker是否存活,也可以获取TaskTracker处理的状态和问题。同时TaskTracker也可以通过心跳里的返回值获取JobTracker给它的操作指令。
  6. 任务分配好后就是执行任务了。在任务执行时候JobTracker可以通过心跳机制监控TaskTracker的状态和进度,同时也能计算出整个Job的状态和进度,而TaskTracker也可以本地监控自己的状态和进度。当JobTracker获得了最后一个完成指定任务的TaskTracker操作成功的通知时候,JobTracker会把整个job状态置为成功,然后当客户端查询Job运行状态时(这个是异步操作),客户端会查到Job完成的通知的。如果Job中途失败,MapReduce也会有相应机制处理,一般而言如果不是程序本身有bug,MapReduce错误处理机制能够保证Job能正常完成。

上面所讲的是早期的MapReduce的架构,现在已经使用Yarn替代了(后面我会单独去讲Yarn),早期的MapReduce主要存在以下问题:

  1. JobTrack单点故障问题

由于JobTrack是MapReduce的集中处理点,如果出单点故障,集群将不能使用,集群的高可用性也就得不到保障。

  1. JobTrack任务过重问题

JobTracker节点完成了太多的任务,会造成过多的资源消耗,当Job任务非常多的时候,会造成很大的内存开销,潜在地增加了JobTracker节点死机的风险。

  1. 容易造成TaskTracker端内存溢出

在TaskTracker端,是以map或reduce的任务数量作为资源的,没有考虑到内存的占用情况,如果两个大内存消耗的任务被调度到一块,很容易出现内存溢出。

  1. 容易造成资源的浪费

在TaskTracker端,把资源强制划分为map任务和reduce任务,如果当系统中只有map任务或者只有reduce任务时,会造成资源的浪费。

3. MapReduce运行机制

在这里插入图片描述
上图时MapReduce运行机制的大体流程:在Hadoop中,一个MapReduce作业会把输入的数据集切分为若干独立的数据块,由Map任务以完全并行的方式处理。框架会对Map的输出先进行排序,然后把结果输入给Reduce任务。作业的输入和输出都会被存储在文件系统中,整个框架负责任务的调度和监控,以及重新执行已关闭的任务。MapReduce框架和分布式文件系统是运行在一组相同的节点,计算节点和存储节点都是在一起的。
下面我将对整个MapReduce的流程处理做详细的分析。

4. MapReduce流程处理

4.1 MapReduce执行过程图

假设有一个非常大的文件,需求是统计文件中每个单词出现的次数,其执行过程图如下图所示:
在这里插入图片描述上图中主要分为Split、Map、Shuffle、Reduce四个阶段,每个阶段在WordCount中的作用如下:

  • Split阶段:首先大文件被切分为多分,假设这里被切分成了3份,每一行代表一份。
  • Map阶段:解析出每个单词,并在后边机上数字1
  • Shuffle阶段:将每一份中的单词分组到一起,并默认按照字母进行排序。
  • Reduce阶段:将相同的单词进行累加。
  • 输出结果
    下面,我将详细的讲解每一个阶段的原理功能。

4.2 Split阶段

Split的大小默认与block对应,也可以由用户任意控制。MapReduce的Split大小计算公式如下:

max(min.split, min(max.split, block))

其中,max.split = totalSize / numSplit, totalSize为文件大小,numSplit为用户设定的map task的个数,默认为1 。而min.split = InputSplit的最小值,具体可以在配置文件中配置参数mapred.min.split.size下设置,不配置时默认为1B,block是HDFS中块的大小。
举个栗子:

现在有一个300MB的文件上传到HDFS上,最终会产生3个map task,而第三个block块里存的文件大小只有44MB,块的大小为128MB,它实际分配空间通过上面的公式计算可以得出是他的实际大小44MB,而非一个块的大小。

4.3 Map阶段

在这里插入图片描述
Map的实现逻辑和Reduce的实现逻辑都是由程序员来完成的,其中Map的个数和Split的个数对应起来,也就是说一个Split切片应该对应一个Map任务,关于Reduce的默认是1,当然这个程序员可以自行配置。另外需要注意的是,一个程序可能只有一个Map任务却没有Reduce任务,也可能是多个MapReduce程序串接起来,比如把第一个MapReduce的输出结果当作第二个MapReduce的输入数据,第二个MapReduce的输出结果当作第三个MapReduce的输入数据,最终完成一个任务,实际上在具体应用当中这种情况是常见的。

4.4 Combiner阶段

Combiner阶段这里作为一个补充说明:
Conbiner阶段是程序员可以选择的,Combiner其实也是一种Reduce操作,Combiner是一个本地化的Reduce操作,它是Map运算的后续操作,主要是在Map计算出中间文件前做一个简单的合并重复key值的操作
举个栗子:

对文件里的单词频率做统计,Map计算时候如果碰到一个Hadoop的单词就会记录为1,但是这篇文章里Hadoop可能会出现n多次,那么Map输出文件冗余就会很多,因此在Reduce计算前对相同的key做一个合并操作,那么文件会变小,这样就提高了传输效率,毕竟hadoop计算宽带资源往往是计算的瓶颈也是最为宝贵的资源,但是Combiner操作是有风险的,使用它的原则是Combiner的输入不会影响到Reduce计算的最终输入。

注意:如果计算只是求总数,最大值,最小值可以使用combiner,但是做平均值计算使用combiner的话,最终的reduce计算结果就会出错。

4.5 Shuffle阶段

Shuffle又叫“洗牌”,它起到连接Map任务与Reduce任务的作用。所谓Shuffle过程可以大致的理解成:怎样把map task的输出结果有效地传送到reduce输入端。也可以这样理解, Shuffle描述着数据从map task输出到reduce task输入的这段过程。
在这里插入图片描述上图表示的是Shuffle的整个过程,在Hadoop这样的集群环境中,大部分map task与reduce task的执行是在不同的节点上。当然很多情况下Reduce执行时需要跨节点去读取其它节点上的map task结果,并存储到本地。如果集群正在运行的job有很多,那么task的正常执行对集群内部的网络资源消耗会很严重。这种网络消耗是正常的,我们不能限制,能做的就是最大化地减少不必要的消耗。另外在节点内,相比于内存,磁盘IO对job完成时间的影响也是比较大的,spark 就是基于这点对hadoop做出了改进,将map和reduce的所有任务都在内存中进行,并且中间接过都保存在内存中,从而比hadoop的速度要快100倍以上。从最基本的要求来说,我们对Shuffle过程希望做到:

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

4.5.1 Shuffle的前半生

Shuffle的前半生即为在Map阶段,主要分为四个阶段:split过程、partition过程、溢写过程、merge过程,下面我将结合WordCount的例子分别讲解:

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

我们已知mapper的输出是这样一个key/value对: key是“aaa”, value是数值1。因为当前map端只做加1的操作,在reduce task里才去合并结果集。在有多个reduce task的前提下,到底当前的“aaa”应该交由哪个reduce去做呢,这个主要有partition来决定,下面就说明如何决定由哪个reduce去做这个事情。

MapReduce提供Partitioner接口,它的作用就是根据key或value以及reduce的数量来决定当前的这对输出数据最终应该交由哪个reduce task处理。默认是对key hash后再以reduce task数量取模。默认的取模方式只是为了平均reduce的处理能力,如果用户自己对Partitioner有需求,可以自己重新实现partition的接口并设置到job上即可。
在我们的例子中,“aaa”经过Partitioner后返回0,也就是这对值应当交由第一个reducer来处理。接下来,需要将数据写入内存缓冲区中,缓冲区的作用是批量收集map结果,减少磁盘IO的影响。我们的key/value对以及Partition的结果都会被写入缓冲区。当然写入之前,key与value值都会被序列化成字节数组。
3. 溢写过程
内存缓冲区是有大小限制的,默认是100MB,也可以通过设置配置文件中的参数mapreduce.task.io.sort.mb来设置。当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。
  • 如果client设置过Combiner,那么现在就是使用Combiner的时候了。将有相同key的key/value对的value加起来,减少溢写到磁盘的数据量。Combiner会优化MapReduce的中间结果,所以它在整个模型中会多次使用。那么哪些场景才能使用Combiner呢?从这里分析,Combiner的输出是Reducer的输入,Combiner绝不能改变最终的计算结果。所以从我的想法来看,Combiner只应该用于那种Reduce的输入key/value与输出key/value类型完全一致,且不影响最终结果的场景。比如累加,最大值等。Combiner的使用一定得慎重,如果用好,它对job执行效率有帮助,反之会影响reduce的最终结果。
  1. merge过程
    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够得着的某个本地目录内。每个reduce task不断地通过RPC从JobTracker那里获取map task是否完成的信息,如果reduce task得到通知,获知某台TaskTracker上的map task执行完成,Shuffle的后半段过程开始启动。

4.5.2 Shuffle的后半生

简单地说,reduce task在执行之前的工作就是不断地拉取当前job里每个map task的最终结果,然后对从不同地方拉取过来的数据不断地做merge,也最终形成一个文件作为reduce task的输入文件。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有三种形式:内存到内存、内存到磁盘、磁盘到磁盘。
    默认情况下第一种形式不启用,让人比较困惑。当内存中的数据量到达一定阈值,就启动内存到磁盘的merge。与map 端类似,这也是溢写的过程,这个过程中如果你设置有Combiner,也是会启用的,然后在磁盘中生成了众多的溢写文件。第二种merge方式一直在运行,直到没有map端的数据时才结束,然后启动第三种磁盘到磁盘的merge方式生成最终的那个文件。
  3. Reducer的输入文件
    不断地merge后,最后会生成一个“最终文件”。为什么加引号?因为这个文件可能存在于磁盘上,也可能存在于内存中。对我们来说,当然希望它存放于内存中,直接作为Reducer的输入,但默认情况下,这个文件是存放于磁盘中的。当Reducer的输入文件已定,整个Shuffle才最终结束。然后就是Reducer执行,把结果放到HDFS上。

4.5.3 Shuffle的人生意义

Shuffle产生的意义是什么?

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

4.6 Reduce阶段

在这里插入图片描述1. 当Map tasks成功结束时,会通知负责的TaskTracker,,然后消息通过JobTracker的heartbeat 传给JobTracker。这样,对于每一个 Job,JobTracker知道map output和map tasks的关联。Reducer内部有一个thread负责定期向 JobTracker询问map output的位置,直到reducer得到所有它需要处理的map output的位置。
2. Reducer 的另一个thread会把拷贝过来的map output file merge成更大的file. 如果map task被configure成需要对 map output进行压缩,那reduce还要对 map 结果进行解压缩。当一个reduce task所有的map output都被拷贝到一个它的host上时,reduce 就要开始对他们排序了。
3. 排序并不是一次把所有 file 都排序,而是分几轮。每轮过后产生一个结果,然后再对结果排序。最后一轮就不用产生排序结果了,而是直接向 reduce 提供输入。这时,用户提供的 reduce函数就可以被调用了。输入就是map任务产生的key value对.
4. 同时reduce任务并不是在map任务完全结束后才开始的,map任务有可能在不同时间结束,所以reduce任务没必要等所有map任务都结束才开始。事实上,每个reduce任务有一些threads专门负责从map主机复制map输出(默认是5个)。
5. 最终结果输出到HDFS上。

5. 灵魂拷问

5.1 当缓冲区快满的时候缓冲区的数据该如何处理?

每个map task都有一个内存缓冲区,存储着map的输出结果,当缓冲区快满的时候需要将缓冲区的数据以一个临时文件的方式存放到磁盘,当整个map task结束后再对磁盘中这个map task产生的所有临时文件做合并,生成最终的正式输出文件,然后等待reduce task来拉数据。

5.2 MapReduce提供Partitioner接口,它的作用是什么?

MapReduce提供Partitioner接口,它的作用就是根据key或value及reduce的数量来决定当前的这对输出数据最终应该交由哪个reduce task处理。默认对key hash后再以reduce task数量取模。默认的取模方式只是为了平均reduce的处理能力,如果用户自己对Partitioner有需求,可以订制并设置到job上。

5.3 Combiner的作用?

下面先看:

如果我们有10亿个数据,Mapper会生成10亿个键值对在网络间进行传输,但如果我们只是对数据求最大值,那么很明显的Mapper只需要输出它所知道的最大值即可。这样做不仅可以减轻网络压力,同样也可以大幅度提高程序效率。

总结:网络带宽严重被占用降低了程序执行效率

假设使用美国专利数据集中的国家一项来阐述数据倾斜这个定义,这样的数据远远不是一致性的或者说平衡分布的,由于大多数专利的国家都属于美国,这样不仅Mapper中的键值对、中间阶段(shuffle)的键值对等,大多数的键值对最终会聚集于一个单一的Reducer之上,压倒这个Reducer,从而大大降低程序的性能。

总结:单一节点承载过重降低程序性能

而MapReduce的一种优化手段,便是使用Combiner。由于每一个map都可能会产生大量的本地输出,Combiner的作用就是对map端的输出先做一次合并,以减少在map和reduce节点之间的数据传输量,以提高网络IO性能。

5.4 哪些场景才能使用Combiner呢?

Combiner的输出是Reducer的输入,Combiner绝不能改变最终的计算结果。所以从我的想法来看,Combiner只应该用于那种Reduce的输入key/value与输出key/value类型完全一致,且不影响最终结果的场景。比如累加,最大值等。Combiner的使用一定得慎重,如果用好,它对job执行效率有帮助,反之会影响reduce的最终结果。

5.5 Merge的作用是什么?

最终磁盘中会至少有一个这样的溢写文件存在(如果map的输出结果很少,当map执行完成时,只会产生一个溢写文件),因为最终的文件只有一个,所以需要将这些溢写文件归并到一起,这个过程就叫做Merge。

5.6 reduce中Copy过程采用是什么协议?

Copy过程,简单地拉取数据。Reduce进程启动一些数据copy线程(Fetcher),通过HTTP方式请求map task所在的TaskTracker获取map task的输出文件。

6. 小结

以上讲解为MapReduce1.0的过程,而且现在MapReduce已经升级到了2.0版本,被Yarn兼并了,但是并不意味着MapReduce1.0被淘汰,在Yarn中的MRYarnClild模块中基本上是是采用MapReduce1.0的解决思路,MRv2具有与 MRv1相同的编程模型和数据处理引擎,唯一不同的是运行时环境。MRv2 是在 MRv1基础上经加工之后,运行于资源管理框架YARN之上的计算框架MapReduce。它运行时环境不再由 JobTracker 和 TaskTracker 等服务组成,而是变为通用资源管理系统YARN和作业控制进程 ApplicationMaster,其中:YARN 负责资源管理和调度,而 ApplicationMaster 仅负责一个作业的管理。简言之,MRv1 仅是一个独立的离线计算框架, 而 MRv2 则是运行于 YARN 之上的 MapReduce。YARN后期会讲到。。
恰饭去。。。。

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值