MapReduce工作原理学习

MapReduce工作原理学习

MapReduce概述

2004年,google在OSDI 2014会议上发表了MapReduce(MapReduce: Simplified Data Processing on Large Clusters)编程模型,它使得不具备并行计算和分布式处理系统开发经验的程序员也可以有效利用分布式系统的丰富资源。

MapReduce的设计是为了处理海量的原始数据,它将并行计算、容错、数据分布、负载均衡等复杂的细节封装起来,使得程序员可以专注于编写Map和Reduce函数。利用MapReduce模型,再结合用户实现的Map和Reduce函数,可以非常容易的实现大规模并行计算。另外,MapReduce自带的“re-execution”功能,也提供了初级的容灾实现方案。

简而言之,MapReduce的主要贡献就是通过简单的接口即可实现自动的并行化和大规模分布式计算,通过使用MapReduce模型接口可以实现在大量普通计算机上的高性能计算。

MapReduce编程模型的原理是:利用一个输入键值对(key/value pair)集合来产生一个输出的键值对集合。该过程的实现需要用户提供自定义的Map和Reduce函数。
其中,Map函数接受一个输入的键值对,然后产生一个中间的键值对集合,然后MapReduce库把所有key相同的中间value值组合起来传递给Reduce函数。

Reduce函数接受一个中间key和该key对应的value值集合,然后将这些value值融合得到一个较小的value值集合。通常情况下,每次Reduce调用只产生0个或1个输出value。中间values通过一个迭代器传递给用户的reduce函数,这使得我们可以处理由于太大而无法全部填入内存中的values列表。

执行过程总览

MapReduce的总体执行过程如图1所示:
这里写图片描述

如图1所示,MapReduce的工作过程按照(1),(2),(3),(4),(5),(6)的顺序执行。
(1) 用户程序首先调用MapReduce库将输入文件分成M个数据片段,每个数据片段的大小通常为16MB到64MB(用户可以通过可选参数来控制每个片段的大小)。之后,在计算机集群中复制大量的程序副本。
(2) 在(1)中复制的程序副本中有一个特殊的程序叫做master,除此之外其它都是worker,由master分配任务。有M个map任务和R个reduce任务将被分配。master为每个空闲的worker分配一个map任务或reduce任务。
(3) 被分配了map任务的worker读取相应的输入片段内容,它从输入数据中解析出键值对然后将每个键值对传递给用户定义的Map函数。Map函数产生的中间键值对将被缓存在内存中。
(4) 缓存中的键值对被周期性的写入到被分区函数分为R个区的本地磁盘中,这些缓存键值对在磁盘中的地址被回传给master,master负责将这些存储地址传递给reduce workers。
(5) 当一个reduce worker接收到master传递来的数据存储地址后,它通过远程调用的方法从map worker所在磁盘中读取这些缓存数据。当一个reduce worker读取了所有中间数据后,将这些数据按照中间key进行排序,使得相同key对应的所有数据聚集在一起。这里的排序过程是必须的,因为许多不同的key会映射到相同的reduce任务上。如果中间数据的数据量过大而无法在内存中完成排序,那么就需要进行外部排序。
(6) reduce worker在排好序的中间数据上进行迭代,对于每一个唯一的中间key,它将该key和对应的中间值集合传递给用户的Reduce函数。Reduce函数的输出被追加到该reduce分区最终的输出文件中。
(7) 当所有map和reduce任务完成后,master唤醒用户程序。此时,在用户程序中对MapReduce的调用返回到用户代码中。

当上述过程成功完成之后,mapreduce执行后的输出存放在R个输出文件中(每个reduce任务产生一个输出文件,文件名由用户指定)。通常,用户不需要将这R个输出文件结合为一个–他们通常将这些文件作为另一个MapReduce调用的输入或者将它们用在另一个可以处理输入为多个分割文件的分布式应用中。

Master数据结构

对于每个map任务和reduce任务,master中保存着它们的状态(空闲,工作中或完成)和非空闲worker的标志。
master是一个管道,它将中间文件区域的位置信息从map任务传递给reduce任务。因此,对于每个已经完成的map任务, master存储着map任务产生的R个中间文件存储区域的位置和大小。当map任务完成时,master接收到关于这些位置和大小的更新信息。这些信息被增量式地推送给正在进行reduce任务的worker。

容错

由于MapReduce库的设计是为了使用成百上千的计算机来处理海量的数据,因此它必须具有很好的容错机制。

Worker失效

master周期性地ping每个worker。如果在一定时间内,master没有接收到某个worker的响应,master就把这个worker标记为失效。由该失效worker完成的map任务被重置为它们初始的空闲状态,这样就可以将这些任务重新分配给其它worker进行完成。相似的,任何由该失效worker完成的正在处理中的map任务或reduce任务也被重置为空闲状态,这样就可以将它们重新分配给其它worker完成了。

已完成的map任务也需要重新执行,这是因为它们的输出存储在已失效的机器的本地磁盘中,所以我们无法获得这些输出。已完成的reduce任务不需要重新执行,这是因为它们的输出存储在全局的文件系统中。

当一个map任务先在worker A上执行,之后因为A失效而转移到worker B执行时,所有执行reduce任务的worker都会得到重新执行的通知。任何还没有从worker A读取数据的reduce任务都将从worker B读取数据。

MapReduce对大规模worker失效的情况具有迅速恢复的能力。例如,在一个MapReduce执行期间,一个正在运行的集群由于网络维护而造成了80台机器在几分钟之内无法访问,此时,MapReduce master只需要重新执行这些不可访问的worker机器即可,然后继续执行未完成的任务,直到最终完成整个MapReduce操作。

Master失效

一种简单的解决方法是让master周期性地写master数据结构的检查点。如果该master任务挂掉了,可以从上一个检查点状态从新开始一个新的副本。然而,在只有一个master的情况下,如果它失效,那么这种方法就无法解决了。在这种情况下,目前的实现方法是如果该master失效就中止这个MapReduce操作。客户可以检查这个状态并且按需重新执行MapReduce操作。

失效处理机制

(原文:Semantics in the Presence of Failures)
当用户提供的map和reduce操作对于输入值是确定的函数时,我们的分布式实现所产生的结果和整个程序顺序执行所产生的结果具有相同的输出。

我们依赖于map和reduce任务输出的原子提交(译注:每一次提交都认为是不可分割的,提交失败则意味全部没有提交,而不会发生因网络中断等异常造成只提交成功了部分的情况)来实现这个特性。每一个运行中的任务都将它的输出写到私有的临时文件中。一个reduce任务产生一个这样的文件,一个map任务产生R个这样的文件(每个reduce任务对应一个)。当一个map任务完成时,该worker发送一条包含R个临时文件名字的消息给master。如果master从一个已完成的map任务再次接收到一条完成消息,那么它将忽略该消息。否则,master将R个文件的名字记录在它的数据结构中。

当一个reduce任务完成时,reduce worker自动将它的临时输出文件重命名为最终的输出文件。如果在多台机器上执行相同的reduce任务,将执行多个重命名调用来产生相同的最终输出文件。我们使用底层文件系统提供的原子性重命名操作来确保最终的文件系统中只包含reduce任务一次执行所产生的数据。

由于绝大多数map和reduce操作是确定性的,而且该MapReduce的实际处理机制(senmantics)等价于顺序执行,因此程序员可以很容易的推导出他们程序的执行行为。当map或reduce操作是非确定性的时候,我们提供了虽然比较弱但是仍然合理的处理机制。在存在不确定性操作的情况下,单个reduce任务R1的输出等价于这个非确定性程序的顺序执行所产生的输出。然而,对于另一个不同的reduce任务R2,它的输出可能对应于这个非确定性程序按不同顺序执行所产生的输出。

考虑map任务M和reduce任务R1和R2。假设 e(Ri) Ri 已经提交了的执行(也就是说只有一个这样的执行)。这里存在一个较弱的失效处理机制,因为 e(R1) 可能读取了M的某一次执行所产生的输出,而 e(R2) 可能读取了M的其它执行所产生的输出。

存储位置

在我们的计算环境中,网络带宽是一个相对稀有的资源,我们通过将输入数据(按GFS进行组织)存储在集群中各机器的本地磁盘中来节省网络带宽。
GFS(Google File System)将每个文件切分为64MB的blocks,并且将每个block的多个副本(通常3个)存储到不同的机器上。MapReduce的master在分配map任务时会考虑这些输入文件的位置信息,尝试将map任务分配给一个包含有对应输入数据副本的机器,如果分配失败,master将把map任务分配给一个与存储有该任务的输入数据副本相邻的机器(也就是说分配给一个处于相同网络交换机中并包含有所需数据的机器)。当在一个集群中运行大的MapReduce操作时,大部分数据是在本地读取的,因此不会消耗网络带宽。

任务粒度

如前所述,我们进一步将map阶段分为M个片段,将reduce阶段分为R个片段。理想情况下,M和R会比worker机器的数量大得多。每一个worker进行许多不同的任务有助于改善动态负载均衡,而且当某个worker失效时也可以加速它的恢复速度:它已经完成的大量map任务都可以重新分配给其它worker机器上执行。

实际上,在实现时,M和R的大小是有一些客观限制的。这是因为master进行任务分配决策的复杂度是O(M+R),并且需要在内存中使用O(M*R)大小的空间来保存之前所说的状态。(但是,内存使用的常数因子是小的,这是因为对于每一个map/reduce任务对,O(M*R)个状态只包含大约一个字节的数据。)

R通常由用户进行设置,这是因为每个reduce任务的输出最终都会写入到一个单独的输出文件中。实际上我们也倾向于对M进行设置,通过设置使得分配给每个单独任务的输入数据大小大概是16MB到64MB之间(此时,上节所说的位置最优才是最有效的)。另外,我们把R值设置为所要使用的worker机器数量的一个小的倍数。当使用2000台worker机器进行MapReduce计算时,我们通常将M设为200000,将R设置为5000.

备份任务

造成MapReduce操作所需总时间过长的一个常见原因是“掉队者”:也就是在完成最后几个map或reduce任务时所花费时间过长的机器。掉队者出现的原因有很多,例如,一台机器由于磁盘损坏而需要进行频繁地纠错操作,这使得它的读取性能从30MB/s下降到1MB/s。如果集群调度系统在该机器上还分配了其它任务,这也会使得MapReduce代码的执行速度由于CPU、内存、磁盘或网络带宽的争夺而变得缓慢。我们最近遇到的一个问题是,机器的初始化代码中的一个bug导致处理器缓存被关闭,这导致受影响机器的运算性能下降了上百倍。

我们使用一个通用的机制来缓和掉队者问题,

……

猜你喜欢

转载自blog.csdn.net/yingyujianmo/article/details/52590242