MapReduce:在大规模集群上的数据处理简化(上)

MapReduce:在大规模集群上的数据处理简化(上)

         摘要:MapReduce是一种编程模型和一种处理和生成大数据集合的相关实现。用户可以特化一个map函数用来处理一个key/value对用来生成一个中间的key/value对,然后用一个reduce函数归并所有的key相同的相关联的value。很多现实世界的任务可以再这个模型中表现出来,正如在这篇论文中描述的这样。

         采用这种函数风格的编写的程序会被自动并行化并且运行在一个由商业机器组成的大规模集群上。在系统运行期间会关注输入数据的划分的详细信息,在一个机器集群中的程序运行的调度,处理机器故障以及管理需要的机器之间的通信。这允许程序员不需要有关于并行化和分布式系统的经验就能很容易的利用大型分布式系统的资源。

         我们实现的MapReduce运行在一个大型商业机器集群上并且是高度可扩展的:一个典型的MapReduce计算在数以千台的电脑上处TB量级的数据。程序员都发现这个系统很容易使用:数百个MapReduce程序已经被实现了而且超过1000个任务会在估计集群上被执行。

 

1.      简介

在过去的5年里,作者还有很多其他的在Google的人实现了数以百计的为了特殊目的的处理大量原始数据的程序,比如爬取到的文档,Web请求日志等等。为了计算不同种类的派生数据,比如逆转目录,不同的表示Web文件的图形结构,在每台主机上爬取的很多页面的摘要信息,在给定的一天内的最频繁查询集合等等。大多数的程序在概念上说是直接明了的。然而,输入数据经常规模很大而且计算必须是分布式跨越成百上千的机器上以使得在合理的时间能完成。怎样划分计算,分布数据,处理故障使得一个搬来很简单的计算问题变得需要大量的复杂代码才能解决。

           作为对于这个复杂问题的反应,我们设计了一种新的抽象模式使得我们表达我们过去做的简单的计算但是隐藏了很多并行化,容错性,数据分布和负载均衡的细节在一个库里。我们的抽象是通过mapreduce作为基元组建起来的采用Lisp或者很多其他的函数式语言展现的。我们实现了大多数我们的计算涉及到了使用一个map操作把每一个在我们输入中的逻辑记录去计算出一个中间key/value对,然后应用一个reduce操作到所有具有相同keyvalue,使得所有生成的数据适当地结合起来。我们采用的函数式模型采用用户定义的mapreduce操作允许我们简单地并行化大规模计算并且使用重运行作为基本机制用来实现故障容忍性。

           这个工作的主要贡献是作为一个简单而又有力的接口使得并行化和分布式的大规模计算实现自动化,结合了这个接口的实现使大规模的商业电脑集群达到了很高的性能。

            2部分描述了基本编程模型并且给出了一些样例,第3部分描述了一种MapReduce接口的为我们的基于集群的计算环境的定制实现。第4部分描述了程序模型中我们发现的有效的优化。第5部分展现了我们方法的的实现对于一些任务的测量值。第6部分探索了MapReduceGoogle的使用包括我们把它作为我们的生产指标系统的一个重写,第7部分讨论了相关了未来的工作。

2.      计算需要用到一些输入的key/value的对的集合,并且产生一个key/value对的集合,MapRedu库的使用者需要用MapReduce两个函数来表达计算过程。

Map需要用户编写,读入一些输入对然后产生一个中间的key/value对的集合。MapReduce库把所有的具有相同的中间key的中间value值聚合在一起然后把他们传递给Reduce函数。Reduce函数需要用户编写,接受一个中间key值以及和这个key相关的value的集合,它聚合这些值产生一个更小的值的集合。典型的Reduce调用只有0个或者一个输出值。提供到Reduce函数的中间值只通过一个迭代器实现的。这就解决了当这些值的集合的列表过大时在内存中不好存放的问题。

2.1样例

想想下面这个问题,在一个文件的大规模集合中统计每一个单词出现的数量,用户可能会写出类似于下面代码的伪代码:

map(String key, String value):

// key: document name

// value: document contents

for each word w in value:

EmitIntermediate(w,"1");

 

reduce(String key, Iterator values):

// key: a word

// values: a list of counts

int result = 0;

for each v in values:

result += ParseInt(v);

Emit(AsString(result));

Map函数对于每一个出现的单词加上出现的次数(在这个例子中是简单的加1)。而reduce函数则对于每一个单词把所有的计数加起来。

此外,用户还要写代码来填充mapreduce定义对象使用输入和输出文件的名字,可选的通信参数。用户然后调用MapReduce函数,传入定义对象。用户的代码会和MapReduce的库(采用C++实现)链接在一起,附录A包含了这个样例中整个程序文本。

         2.2类型
         尽管之前的伪代码输入和输出都以字符串的形式,但概念上说mapreduce函数需要被用户提供成以下相关联的类型:

         map (k1,v1) –>  list(k2,v2)

reduce (k2,list(v2)) ->  list(v2)

例如,输入的keyvalue值被写成和输出的keyvalue是在不同域的。此外,中间的keyvalue值具有和输出的keyvalue具有相同的域。

我们的C++实现传入字符串从用户定义的函数到用户定义的函数,而且把它交给用户代码去做字符串和适当类型之间的转换。

2.3更多样例

这里有一些有趣程序的简单样例,他们能被表达成MapReduce的计算形式。

分布式查找:map函数发出一行如果它匹配了给定的模式。Reduce函数是一个恒等函数只将提供的中间数据拷贝到输出文件。

URL访问频率计数:map函数处理web页面日志中的请求然后输出(URL1),reduce函数将所有URL相同的value加起来然后得出(URLtotal count)对。

反转web链接图:map函数输出(targetsource)对对于在一个叫source的页面链接到的targetReduce函数链接所有的与给定的目标URL相关联的全部的原URLs然后发出:

target; list(source))(统计访问一个目标URL的前一个URL吧)

         每一台主机的搜素词向量:一个检索词向量总结了出现在一个文档或者一个文档集合中最重要的词成为一个(wordfrequency)对的列表。Map函数对于每一个文档发出一个(hostnameterm vector)对(hostname从文件的URL中提取出来的),reduce函数遍历一台指定主机的全部检索词向量,它把这些检索词向量加起来,出去频率最低的检索词,然后发出一个最终的(hostname term vector)对。

         反向索引:map函数解析每一个文件,发出一个(worddecument id)对的序列,reduce函数接受一个给定次的全部对,对这些对应的文件id进行排序发出(wordlist(document id))对,全部输出的集合组成了一个简单的反向索引。这样保持词的位置很容易扩展计算规模。

         分布式排序:map函数从每一个记录中提取出key,然后发出一个(keyrecord)对,reduce函数发出所有的没有改变的对,计算需要依赖在4.1部分描述的划分条件和4.2中描述的排序性质。

 

3.      实现

MapReduce接口可能是具有多种不同实现的。怎样做出正确的选择是依赖于环境的。比如,我们的实现可能是适用于小型共享内存机器和大型NUMA多处理器而且还有甚至大规模具有网络连接的机器群。

           这个章节描述了一种目的在于Google广泛使用的计算环境(具有交换机以太网的大规模商业集群PC)的实现。在我们的环境中:

(1)      机器都是典型的双核X86处理器运行Linux,每台机器具有2-4G的内存。

(2)      商业网络硬件被使用:典型的100Mb每秒或者1Gb每秒的级别,但是平均考虑起来只有一半的带宽。

(3)      一个集群由成百上千的机器组成,因此机器故障是很常见的。

(4)      存储是由便宜直连到独立机器的的IDE磁盘提供的,一个在内部开发的分布式文件系统被用来管理在这些磁盘上存储的数据。这个文件系统利用副本来在不可靠的硬件基础上提供可用性和可靠性。

(5)      用户提交作业到调度系统,每一个作业由任务的集合组成,作业被调度系统映射到集群中的一些可用的机器集合。

1 运行概观

 

         3.1运行概观

         通过自动划分将输入数据划分成M片的集合跨越到多台机器分布式调用map函数。输入片能在不同的机器上被并行化处理。Reduce函数调用是划分中间key值成R片(比如使用hashkeymod R)进行分布式调用。划分的份数和划分函数都是由用户自定义的。

1向展示了我们的实现中mapreduce的整体流程。当用户程序调用mapreduce函数,接下来的动作序列出现了:

(1)            在用户程序中的mapreduce库首先将输入划分为M片,典型的是每片大小为16MB-64MB(可以由用户通过可选参数控制),然后开始了在集群机器上程序的多份拷贝。

(2)            程序的每一个副本都是特化的-master,剩下都是worker,由master来指派任务,有Mmap任务和Rreduce任务需要指派,master寻找空闲的worker然后指派它们每台机器一个map任务或者reduce任务。

(3)            一个worker被指派到一个map任务后读取对应的输入片的内容。它从输入数据中解析key/value对出来并且把每一个对传递给用户定义的map函数。由map函数产生的中间key/value对被缓存在内存中。

(4)            缓存的对会被周期性的写入到磁盘中,被划分函数划分到R个区域中。这些缓存对在本地磁盘的位置会传回给master,因为master需要转发这些位置给分配到reduce任务的workers

(5)            当一个reduce workermaster那里获取了这些位置,它使用远程过程调用去从map worker的本地磁盘读取缓存数据。当一个reduce worker已经读完了全部的中间数据,它把它们用中间key排列因此所有的出现相同key的就会聚合在一起。排序是需要的因为典型的很多不同的key值会被映射到相同的reduce任务,如果中间数据的规模过大无法放入内存,则还需要用到外排序。

(6)            Reduce worker迭代全部排序过后的中间数据然后对于每一个的独特的统计过的中间key,它传入key和对应的中间value的集合到用户的Reduce函数,这个reduce划分的Reduce函数的输出被添加到一个最终的输出文件。

(7)            当所有的map任务和reduce任务都已经被完成之后,master唤醒用户程序,在这时,mapreduce调用用户程序并把结果返回给用户代码。

在成功的完成了之后,mapreduce运行的输出在R个输出文件(每一个reduce对应一个,文件名也是用户定义的)中是可用的。用户不需要去合并这R个输出文件成为一个输出文件,它们经常把这些文件作为输入传入到另一个mapreduce应用,或者在一个是能处理划分成多个文件作为输入的分布式应用中使用它们。

3.2master数据结构

Master保持了很多数据结构,对于每一个map任务和reduce任务,它存储状态(空闲,正在处理中或者完成),以及worker机器(对于不空闲任务)的身份识别。

         Master是在本地中间文件区域从map任务和reduce任务的传播管道。因此,对于每一个完成的map任务,master存储了被map任务产生的R个中间文件区域的位置和大小。在map任务完成之后位置和大小信息接收到之后就会被更新。这些信息被增量推送到正在运行状态的reduce任务的worker中。

3.3故障容忍性

因为mapreduce库是被设计成帮助处理大量数据使用成百上千台机器处的,所以库必须能非常优雅的容忍故障。

Worker故障

Master会周期性的ping每一个worker,如果在一个确定的时间里,没有收到一个worker的回应,master就将worker标记为故障。所有的这个worker完成的map任务都被重置为初始的空闲状态,因此又可以被调度给其他的worker。同样的,任何map任务或者reduce任务在一个故障的worker中都会被重置为空闲并且可以被重新调度。

故障机器上的完成的map任务也需要被重新运行时因为输出是储存在故障机器的本地磁盘中而现在是不可访问的。完成的reduce任务不需要重新运行因为它们的输出是储存在一个全局文件系统中。

当一个map任务第一次被一个workerA运行然后被workerB运行(因为A故障了),所有运行reduce任务的worker都记录下了这个重运行,任何reduce任务没有还没有从workerA读取数据的都从workerB那里读取数据。

Mapreduce对于大规模worker故障是具有弹性的。例如,在一个mapreduce操作期间,网络维护使得运行的集群中具有80台机器在几分钟内都不可被连接,mapreduce会很简单的重运行这些不能被连接到的机器的任务并且继续向前执行,最终将完成这个mapreduce操作。

         Master故障

可以简单地让master周期性的写入上面描述的master数据结构的检查点集。如果master任务卡死,一个新的副本会从最后一个检查点的状态开始。然而,给定只有一个master,它发生故障就没戏了;因此我们现在的实现会终端mapreduce计算如果master故障,客户机能发现这种状况如果它还想执行可以重试。

         出现故障的语义

当用户提供mapreduce操作是输入值的确定性函数(每次运行结果是确定的)时,我们的分布式实现会产生相同的结果就像没有错误的顺序执行整个程序一样。

我们依靠原子化提交mapreduce任务输出来保持这个性质。在进程中的每一个任务把它的输出写到私有临时文件。Reduce任务会产生一个这样的文件,一个map任务产生R个这样的文件(每个reduce任务一个)。当一个map任务结束,worker会发送一个信息到master,信息中包括R个临时文件的名字。如果master收到了一个已经完成的map任务的完成信息,它会忽略这条信息。否则,它会把R个文件的名字记录在master数据结构中。

当一个reduce任务结束,reduce worker自动把它的临时输出文件命名为最终输出文件,如果相同的reduce任务在多台机器上被执行,多重重命名调用将会在同一个最终输出文件上被调用。我们依靠基础文件系统提供的原子化重命名操作保证最终文件系统装包只包括一个reduce运行产生的数据。

绝大多数的mapreduce操作都是确定性的,但是我们语义上是与顺序执行等价的,这使得程序员很容易推断它们的程序的行为。当mapreduce任务都不是确定性的,我们提供较弱但是仍然合理的语义。当存在非确定性的的算子,对于一个特定的reduce任务的输出R1会与非确定性程序中的R1的顺序执行的结果相同。然而,一个不同的reduce任务R2可能与另外与另外一种非确定性的顺序执行的结果相同。

(比如一台机随机产生ABC3个数,直接顺序运行得 A = 5 B = 6 C = 7第二次运行 A = 8 B = 9  C = 10;分布式3台机分别随机产生一个数 A= 5 B = 9 C = 11A与第一次相同,B与第二次相同。)

         思考一下map任务Mreduce任务R1R2,让eRi)作为Ri的运行结果,弱语义出现因为eR1)可能会读取M一次运行产生的结果,而eR2)却读取了M的另一次运行产生的结果。

3.4局部性

         网络带宽在我们的计算环境中是一个相当稀缺的资源。我们通过将输入数据(由GFS管理)保存在组成集群中的机器的本地磁盘中的方式来保护网络带宽资源。GFS把每一个文件分成64MB大小的块,而且在不同的机器上存储这些块的多个副本(典型的是3份)。Mapreducemaster得到文件的局部信息并且尝试调度一个map任务使得机器具有对应的副本。如果做不到这些,就尝试调度map任务使得机器与需要的对应数据的距离邻近(例如在同一台交换机上的机器包含输入数据)。当在一个集群上运行大规模的mapreduce操作,大部分的输入数据是在本地读取基本上不消耗网络带宽。

3.5任务粒度(未完待续请看下)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值