Google MapReduce 大数据处理系统

摘要

  • MapReduce是一个设计模型,也是一个处理和产生海量数据的一个相关实现。用户指定一个用于处理一个键值(key-value)对生成一组key/value对形式的中间结果的map函数,以及一个将中间结果键相同的键值对合并到一起的reduce函数。许多现实世界的任务都能满足这个模型,如这篇文章所示。
  • 使用这个功能形式实现的程序能够在大量的普通机器上并行执行。这个运行程序的系统关心下面的这些细节:输入数据的分区、一组机器上调度程序执行、处理机器失败问题,以及管理所需的机器内部的通信。这使没有任何并行处理和分布式系统经验的程序员能够利用这个大型分布式系统的资源。
  • 我们的MapReduce实现运行在一个由普通机器组成的大规模集群上,具有很高的可扩展性:一个典型的MapReduce计算会在几千台机器上处理许多TB的数据。程序员们发现这个系统很容易使用:目前已经实现了几百个MapReduce程序,在Google的集群上,每天有超过一千个的MapReduce工作在运行。

介绍

在过去的5年中,本文作者和许多Google的程序员已经实现了数百个特定用途的计算程序,处理了海量的原始数据,包括抓取到的文档、网页请求日志等,计算各种衍生出来的数据,如反向索引、网页文档的图形结构的各种表示、每个host下抓取到的页面数量的总计、一个给定日期内的最频繁查询的集合等。大多数这种计算概念明确。然而,输入数据通常都很大,并且计算必须分布到数百或数千台机器上以确保在一个合理的时间内完成。如何并行计算、分布数据、处理错误等问题使这个起初很简单的计算,由于增加了处理这些问题的很多代码而变得十分复杂。

为了解决这个复杂问题,我们设计了一个新的抽象模型,它允许我们将想要执行的计算简单的表示出来,而隐藏其中并行计算、容错、数据分布和负载均衡等很麻烦的细节。我们的抽象概念是受最早出现在lisp和其它结构性语言中的map和reduce启发的。我们认识到,大多数的计算包含对每个在输入数据中的逻辑记录执行一个map操作以获取一组中间key/value对,然后对含有相同key的所有中间值执行一个reduce操作,以此适当的合并之前的衍生数据。由用户指定map和reduce操作的功能模型允许我们能够简单的进行并行海量计算,并使用re-execution作为主要的容错机制。

这项工作的最大贡献是提供了一个简单的、强大的接口,使我们能够自动的进行并行和分布式的大规模计算,通过在由普通PC组成的大规模集群上实现高性能的接口来进行合并。

第二章描述了基本的编程模型,并给出了几个例子。第三章描述了一个为我们的聚类计算环境定制的MapReduce接口实现。第四章描述了我们发现对程序模型很有用的几个优化。第六章探索了MapReduce在Google内部的使用,包括我们在将它作为生产索引系统重写的基础的一些经验。第七章讨论了相关的和未来的工作。

编程模型

这个计算输入一个key/value对集合,产生一组输出key/value对。MapReduce库的用户通过两个函数来标识这个计算:Map和Reduce。

  • Map,由用户编写,接收一个输入对,产生一组中间key/value对。MapReduce库将具有相同中间key I的聚合到一起,然后将它们发送给Reduce函数。
  • Reduce,也是由用户编写的,接收中间key I和这个key的值的集合,将这些值合并起来,形成一个尽可能小的集合。通常,每个Reduce调用只产生0或1个输出值。这些中间值经过一个迭代器(iterator)提供给用户的reduce函数。这允许我们可以处理由于数据量过大而无法载入内存的值的链表。

例子:

考虑一个海量文件集中的每个单词出现次数的问题,用户会写出类似于下面的伪码:

Map函数对每个单词增加一个相应的出现次数(在这个例子中仅仅为“1”)。Reduce函数将一个指定单词所有的计数加到一起。

此外,用户使用输入和输出文件的名字、可选的调节参数编写代码,来填充一个mapreduce规格对象,然后调用MapReduce函数,并把这个对象传给它。用户的代码与MapReduce库(C++实现)连接到一起。。附录A包含了这个例子的整个程序。

类型

尽管之前的伪代码中使用了字符串格式的输入和输出,但是在概念上,用户定义的map和reduce函数需要相关联的类型:

  • map       (k1, v1)                      -->         list(k2, v2)
  • reduce   (k2, list(v2))                -->          list(v2)

也就是说,输入的键和值和输出的键和值来自不同的域。此外,中间结果的键和值与输出的键和值有相同的域。

MapReduce的C++实现与用户定义的函数使用字符串类型进行参数传递,将类型转换的工作留给用户的代码来处理。

更多的例子

这里有几个简单有趣的程序,能够使用MapReduce计算简单的表示出来。

  • 分布式字符串查找(Distributed Grep):map函数将匹配一个模式的行找出来。Reduce函数是一个恒等函数,只是将中间值拷贝到输出上。
  • URL访问频率计数(Count of URL Access Frequency):map函数处理web页面请求的日志,并输出<URL, 1>。Reduce函数将相同URL的值累加到一起,生成一个<URL, total count>对。
  • 翻转网页连接图(Reverse Web-Link Graph):map函数为在一个名为source的页面中指向目标(target)URL的每个链接输出<target, source>对。Reduce函数将一个给定目标URL相关的所有源(source)URLs连接成一个链表,并生成对:<target, list(source)>。
  • 主机关键向量指标(Term-Vector per Host):一个检索词向量将出现在一个文档或是一组文档中最重要的单词概述为一个<word, frequency>对链表。Map函数为每个输入文档产生一个<hostname, term vector>(hostname来自文档中的URL)。Reduce函数接收一个给定hostname的所有文档检索词向量,它将这些向量累加到一起,将罕见的向量丢掉,然后生成一个最终的<hostname, term vector>对。
  • 倒排索引(Inverted Index):map函数解析每个文档,并生成一个<word, document ID>序列。Reduce函数接收一个给定单词的所有键值对,所有的输出对形成一个简单的倒排索引。可以通过对计算的修改来保持对单词位置的追踪。
  • 分布式排序(Distributed Sort):map函数将每个记录的key抽取出来,并生成一个<key, record>对。Reduce函数不会改变任何的键值对。这个计算依赖了在4.1节提到的分区功能和4.2节提到的排序属性。

实现

MapReduce接口有很多不同的实现,需要根据环境来做出合适的选择。比如,一个实现可能适用于一个小的共享内存机器,而另一个实现则适合一个大的NUMA多处理器机器,再另一个可能适合一个更大的网络机器集合。

这一章主要描述了针对在Google内部广泛使用的计算环境的一个实现:通过交换以太网将大量的普通PC连接到一起的集群。在我们的环境中:

(1)机器通常是双核x86处理器、运行Linux操作系统、有2-4G的内存。

(2)使用普通的网络硬件—通常是100Mb/s或者是1Gb/s的机器带宽,但是平均值远小于带宽的一半。

(3)由数百台或者数千台机器组成的集群,因此机器故障是很平常的事

(4)存储是由直接装在不同机器上的便宜的IDE磁盘提供。一个内部的分布式文件系统用来管理存储这些磁盘上的数据。文件系统在不可靠的硬件上使用副本机制提供了可用性和可靠性。

(5)用户将工作提交给一个调度系统,每个工作由一个任务集组成,通过调度者映射到集群中可用机器的集合上。

执行概述

通过自动的将输入数据分区成M个分片,Map调用被分配到多台机器上运行。数据的分片能够在不同的机器上并行处理。使用分区函数(如,hash(key) mod R)将中间结果的key进行分区成R个分片,Reduce调用也被分配到多台机器上运行。分区的数量(R)和分区函数是由用户指定的。

图1中显示了我们实现的一个MapReduce操作的整个流程。当用户程序调用MapReduce函数时,下面一系列的行为将会发生(图1中所使用的数字标识将与下面列表中的相对应):

  1. 用户程序中的MapReduce库会先将输入文件分割成M个通常为16MB-64MB大小的片(用户可以通过可选参数进行控制)。然后它将在一个集群的机器上启动许多程序的拷贝。
  2. 这些程序拷贝中的一个是比较特殊的——master。其它的拷贝都是工作进程,是由master来分配工作的。有M个map任务和R个reduce任务被分配。Master挑选出空闲的工作进程,并把一个map任务或reduce任务分配到这个进程上。
  3. 一个分配了map任务的工作进程读取相关输入分片的内容,它将从输入数据中解析出key/value对,并将其传递给用户定义的Map函数。Map函数生成的中间key/value对缓存在内存中。
  4. 缓存中的键值对周期性的写入到本地磁盘,并通过分区函数分割为R个区域。将这些缓存在磁盘上的键值对的位置信息传回给master,master负责将这些位置信息传输给reduce工作进程。
  5. 当一个reduce工作进程接收到master关于位置信息的通知时,它将使用远程调用函数(RPC)从map工作进程的磁盘上读取缓存的数据。当reduce工作进程读取完所有的中间数据后,它将所有的中间数据按中间key进行排序,以保证相同key的数据聚合在一起。这个排序是需要的,因为通常许多不同的key映射到相同的reduce任务上。如果中间数据的总量太大而无法载入到内存中,则需要进行外部排序。
  6. reduce工作进程迭代的访问已排序的中间数据,并且对遇到的每个不同的中间key,它会将key和相关的中间values传递给用户的Reduce函数。Reduce函数的输出追加到当前reduce分区一个最终的输出文件上。
  7. 当所有的map任务和reduce任务完成后,master会唤醒用户程序。这时候,用户程序中的MapReduce调用会返回到用户代码上。

在成功完成后,MapReduce操作输出到R个输出文件(每个reduce任务生成一个,文件名是由用户指定的)中的结果是有效的。通常,用户不需要合并这R个输出文件,它们经常会将这些文件作为输入传递给另一个MapReduce调用,或者在另一个处理这些输入分区成多个文件的分布式应用中使用。

Master数据结构

Master保留了几个数据结构。对于每个Map和Reduce任务,它存储了它们的状态(idle、in-progress或者completed),以及工作进程机器的特性(对于非空闲任务)。

Master是中间文件区域的位置信息从map任务传送到reduce任务的一个通道。因此,对于每个完成的map任务来说,master存储了map任务产生的R个中间文件区域的位置信息和大小。在map任务完成时,master会接收到更新这个含有位置信息和大小信息的消息。信息被增量的传输到运行in-progress的reduce任务的工作进程上。

容错

因为MapReduce库是被设计成运行在数百或数千台机器上帮助处理海量数据的,所以这个库必须能够优雅的处理机器故障。

工作进程故障

Master周期性的ping每个工作进程,如果在一个特定的时间内没有收到响应,则master会将这个工作进程标记为失效。任何由失效的工作进程完成的map任务都被标记为初始idle状态,因此这个map任务会被重新分配给其它的工作进程。同样的,任何正在处理的map任务或reduce任务也会被置为idle状态,进而可以被重新调度。

在一个失效的节点上完成的map任务会被重新执行,因为它们的输出被存放在失效机器的本地磁盘上,而磁盘不可访问。完成的reduce任务不需要重新执行,因为它们的输出被存储在全局文件系统上。

当一个map任务先被工作进程A执行,然后再被工作进程B执行(因为A失效了),所有执行reduce任务的工作进程都会接收到重新执行的通知,任何没有从工作进程A上读取数据的reduce任务将会从工作进程B上读取数据。

MapReduce对于大规模工作进程失效有足够的弹性。比如,在一个MapReduce操作处理过程中,网络维护造成了80台机器组成的集群几分钟内不可达。MapReduce的master会重新执行那些在不可达机器上完成的工作,并持续推进,最终完成MapReduce操作。

Master故障

将上面提到的master数据结构周期性的进行写检查点操作(checkpoint)是比较容易的。如果master任务死掉,一个新的拷贝会从最近的检查点状态上启动。然而,假定只有一个单独的master,它的故障是不大可能的。因此,如果master故障,我们当前的实现是中止MapReduce计算。

当前故障的语义

当用户提供的map和reduce操作是输入确定性函数,我们的分布式实现与无故障序列执行整个程序所生成的结果相同。

我们依靠map和reduce任务输出的原子性提交来实现这个属性。每个in-progress任务将它们的输出写入到一个私有的临时文件中。一个reduce任务产生一个这样的文件,一个map任务产生R个这样的文件(每个reduce任务一个)。当一个map任务完成时,它将发送给master一个消息,其中包括R个临时文件的名字。如果master收到一个已经完成的map任务的完成消息,则忽略这个消息。否则,它将这R个文件名记录在master的数据结构中。

当一个reduce任务完成后,reduce的工作进程自动的将临时文件更名为最终的输出文件,如果相同的reduce任务运行在多台机器上,会调用多个重命名操作将这些文件更名为最终的输出文件。

绝大部分的map和reduce操作是确定性的,事实上,在这种情况下我们的语义与一个序列化的执行是相同的,这使程序开发者能够简单的推出他们程序的行为。当map和/或reduce操作是不确定性的时,我们提供较弱但依然合理的语义。在不确定性的操作面前,一个特定的reduce任务R1的输出与一个序列执行的不确定性程序生成的输出相同。然而,一个不同的reduce任务R2的输出可能与一个不同的序列执行的不确定性程序生成的输出可能一致。

考虑map任务M和reduce任务R1和R2。假定e(Ri)是提交的Ri的执行过程(有且仅有这样一个过程)。e(R1)可能从M的一个执行生成的输出中读取数据,e(R2)可能从M的一个不同执行生成的输出中读取数据,则会产生较弱的语义。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值