Professional Documents
Culture Documents
超大集群的简单数据处理
收件人:
发件人: 崮山路上走 9 遍
抄送:
日期: 2009-03-04
关于: MapReduce: Simplified Data Processing on Large Clusters
摘要
MapReduce 是一个编程模式,它是与处理/产生海量数据集的实现相关。用户指定一个 map 函数,
通过这个 map 函数处理 key/value(键/值)对,并且产生一系列的中间 key/value 对,并且使用
reduce 函数来合并所有的具有相同 key 值的中间键值对中的值部分。现实生活中的很多任务的实
现都是基于这个模式的,正如本文稍后会讲述的那样。
使用这样的函数形式实现的程序可以自动分布到一个由普通机器组成的超大几群上并发执行。run-
time 系统会解决输入数据的分布细节,跨越机器集群的程序执行调度,处理机器的失效,并且管
理机器之间的通讯请求。这样的模式允许程序员可以不需要有什么并发处理或者分布式系统的经验
就可以处理超大的分布式系统得资源。
我们的 MapReduce 系统的实现运行在一个由普通机器组成的大型集群上,并且有着很高的扩展
性:一个典型的 MapReduce 计算处理通常分布到上千台机器上来处理上 TB 的数据。程序员会发
现这样的系统很容易使用:已经开发出来了上百个 MapReduce 程序,并且每天在 Google 的集群
上有上千个 MapReduce job 正在执行。
1 介绍
在过去的 5 年内,Google 的创造者和其他人实现了上百个用于特别计算目的的程序来出来海量的
原始数据,比如蠕虫文档,web 请求 log,等等,用于计算出不同的数据,比如降序索引,不同
的图示展示的 web 文档,蠕虫采集的每个 host 的 page 数量摘要,给定日期内最常用的查询等等。
绝大部分计算都是概念上很简洁的。不过,输入的数据通常是非常巨大的,并且为了能在合理时间
内执行完毕,其上的计算必须分布到上百个或者上千个计算机上去执行。如何并发计算,如何分布
数据,如何处理失败等等相关问题合并在一起就会导致原本简单的计算掩埋在为了解决这些问题
而引入的很复杂的代码中。
因为这种复杂度,我们设计了一种新的东西来让我们能够方便处理这样的简单计算。这些简单计算
原本很简单,但是由于考虑到并发处理细节,容错细节,以及数据分布细节,负载均衡等等细节
问题,而导致代码非常复杂。所以我们抽象这些公共的细节到一个 lib 中。这种抽象是源自 Lisp 以
及其他很多面向功能的语言的 map 和 reduce 概念。我们认识到大部分操作都和 map 操作相关,
这些 map 操作都是运算在输入记录的每个逻辑”record”上,并且 map 操作为了产生一组中间的
key/value 键值对,并且接着在所有相同 key 的中间结果上执行 reduce 操作,这样就可以合并适当
的数据。我们得函数模式是使用用户定义的 map 和 reduce 操作,这样可以让我们并发执行大规模
的运算,并且使用重新执行的方式作为容错的优先机制。
第1页
MapReduce 的主要贡献在于提供了一个简单强大的接口,通过这个接口,可以把大尺度的计算自
动的并发和分布执行。使用这个接口,可以通过普通 PC 的巨大集群,来达到极高的性能。
第二节讲述了基本的编程模式,并且给出了一些例子。第三节讲述了一个面向我们基于集群的计算
环境的 MapReduce 的实现。第四节讲述了一些我们建议的精巧编程模式。第五节讲述了在不同任
务下我们的 MapReduce 实现的性能比较。第六节讲述了在 Google 中的 MapReduce 应用以及尝
试重写了我们产品的索引系统。第七节讲述了相关工作和未来的工作。
2 编程模式
我们的运算处理一组输入的(input)键值对(key/valuepairs),并且产生一组输出的(output)键
值对。MapReduce 函数库德用户用两个函数来表达这样的计算:Map 和 Reduce。
Map 函数,是用户自定义的的函数,处理输入的键值对,并且产生一组中间的(intermediate)键
值对。MapReduce 函数库稽核所有相同的中间键值键 I 的值,并且发送给 Reduce 函数进行处理。
Reduce 函数同样也是用户提供的,它处理中间键值 I,以及这个中间键值相关的值集合。这个函
数合并这些值,最后形成一个相对较小的值集合。通常一个单次 Reduce 执行会产生 0 个或者 1 个
输出值。提供给 Reduce 函数的中间值是通过一个 iterator 来提供的。这就让我们可以处理超过内存
容量的值列表。
2.1 例子
我们考虑这样一个例子,在很大的文档集合中通机每一个单词出现的次数。我们写出类似如下的伪
代码:
map(String key, String value):
// key: document name
// value: document contents
for each word w in value:
EmitIntermediate(w, "1");
2.2 类型
第2页
数有着如下相关类型:
map (k1,v1) list(k2,v2)
reduce (k2,list(v2)) list(v2)
也就是,输入的键和值和输出的键值是属于不同的域的。进一步说,中间的键值是和输出的键值属
于相同的域的。(比如 map 的输出,就是作为 reduce 的输入)。
我们的 C++实现上,把字符串作为用户定义函数的输入和输出,由用户代码来自己识别字符串到
合适的类型。
2.3 其他例子
3 实现
MapReduce 接口可以有很多种不同的实现。应当根据不同的环境选择不同的实现。比如,一个实现
可以适用于小型的共享内存的机器,另一个实现可能是基于大型 NUMA 多处理器系统,还可能有
为大规模计算机集群的实现。
本届描述了 Google 广泛使用的计算环境:用交换机网络[4]连接的,由普通 PC 构成的超大集群。
在我们的环境里:
(1) 每个节点通常是双 x86 处理器,运行 Linux,每台机器 2-4GB 内存。
(2) 使用的网络设备都是常用的。一般在节点上使用的是 100M/或者千 M 网络,一般情况下都
用不到一半的网络带宽。
(3) 一个 cluster 中常常有成百上千台机器,所以,机器故障是家常便饭。
(4) 存储时使用的便宜的 IDE 硬盘,直接放在每一个机器上。并且有一个分布式的文件系统来
管理这些分布在各个机器上的硬盘。文件系统通过复制的方法来在不可靠的硬件上保证可
用性和可靠性。
(5) 用户向调度系统提交请求。每一个请求都包含一组任务,映射到这个计算机 cluster 里的一
第3页
组机器上执行。
3.1 执行概览
第4页
5. 当 master 通知 reduce 的 worker 关于中间 key/value 对的位置时,他调用 remote procedure
来从 map worker 的本地硬盘上读取缓冲的中间数据。当 reduce 的 worker 读到了所有的中间
数据,他就使用中间 key 进行排序,这样可以使得相同 key 的值都在一起。因为有许多不同
key 的 map 都对应相同的 reduce 任务,所以,排序是必须的。如果中间结果集太大了,那么
就需要使用外排序。
6. reduce worker 根据每一个唯一中间 key 来遍历所有的排序后的中间数据,并且把 key 和相关
的中间结果值集合传递给用户定义的 reduce 函数。reduce 函数的对于本 reduce 区块的输出到
一个最终的输出文件。
7. 当所有的 map 任务和 reduce 任务都已经完成了的时候,master 激活用户程序。在这时候
MapReduce 返回用户程序的调用点。
3.3 容错考虑
由于 MapReduce 函数库是设计用于在成百上千台机器上处理海量数据的,所以这个函数库必须
考虑到机器故障的容错处理。
Worker 失效的考虑
master 会定期 ping 每一个 worker 机器。如果在一定时间内没有 worker 机器的返回,master 就认
为这个 worker 失效了。所有这台 worker 完成的 map 任务都被设置成为他们的初始 idel 状态,并
且因此可以被其他 worker 所调度执行。类似的,所有这个机器上正在处理的 map 任务或者 reduce
任务都被设置成为 idle 状态,可以被其他 worker 所重新执行。
在失效机器上的已经完成的 map 任务还需要再次重新执行,这是因为中间结果存放在这个失效的
机器上,所以导致中间结果无法访问。已经完成的 recude 任务无需再次执行,因为他们的结果已
经保存在全局的文件系统中了。
当 map 任务首先由 Aworker 执行,随后被 Bworker 执行的时候(因为 A 失效了),所有执行
reduce 任务的 worker 都会被通知。所有还没有来得及从 A 上读取数据的 worker 都会从 B 上读取
数据。
MapReduce 可以有效地支持到很大尺度的 worker 失效的情况。比如,在一个 MapReduce 操作中
在一个网络例行维护中,可能会导致每次大约有 80 台机器在几分钟之内不能访问。MapReduce
的 master 制式简单的把这些不能访问的 worker 上的工作再执行一次,并且继续调度进程,最后
完成 MapReduce 的操作。
Master 失效
第5页
在 master 中,定期会设定 checkpoint,写出 master 的数据结构。如果 master 任务失效了,可以
从上次最后一个 checkpoint 开始启动另一个 master 进程。不过,由于只有一个 master 在运行,所
以他如果失效就比较麻烦,因此我们当前的实现上,是如果 master 失效了,就终止 MapReduce
执行。客户端可以检测这种失效并且如果需要就重新尝试 MapReduce 操作。
失效的处理设计
当用户提供的 map 和 reduce 函数对于他们的输入来说是确定性的函数,我们的分布式的输出就
应当和在一个整个程序没有失败的连续执行相同。
我们依靠对 map 和 reduce 任务的输出进行原子提交来完成这样的可靠性。每一个 in-progress 任
务把输出写道一个私有的临时文件中。reduce 任务产生一个这样的文件,map 任务产生 R 个这样
的任务(每一个对应一个 reduce 任务)。当一个 map 任务完成的时候,worker 发送一个消息给
master,并且这个消息中包含了这个 R 临时文件的名字。如果 master 又收到一个已经完成的 map
任务的完成消息,他就忽略这个消息。否则,他就在 master 数据结构中记录这个 R 文件。
当一个 reduce 任务完成的时候,reduce worker 自动把临时输出的文件名改为正式的输出文件。如
果再多台机器上有相同的 reduce 任务执行,那么就会有多个针对最终输出文件的更名动作。我们
依靠文件系统提供的原子操作’改名字’,来保证最终的文件系统状态中记录的是其中一个 reduce
任务的输出。
我们的绝大部分 map 和 reduce 操作都是确定性的,实际上在语义角度,这个 map 和 reduce 并
发执行和顺序执行市一样的,这就使得程序员很容易推测程序行为。当 map 和 reduce 操作是非确
定性的时候,我们有稍弱的但是依旧是有道理的错误处理机制。对于非确定性操作来说,特定
reduce 任务 R1 的输出,与,非确定性的顺序执行的程序对 R1 的输出是等价的。另外,另一个
reduce 任务 R2 的输出,是和另一个顺序执行的非确定性程序对应的 R2 输出相关的。
考虑 map 任务 M 和 reduce 任务 R1,R2。我们设定 e(Ri)为已经提交的 Ri 执行(有且仅有一个这
样的执行)。当 e(R1)处理得是 M 的一次执行,而 e(R2)是处理 M 的另一次执行的时候,那么就会
导致稍弱的失效处理了。
3.4 存储位置
在我们的环境下,网络带宽资源是相对缺乏的。我们用尽量让输入数据保存在构成集群机器的本地
硬盘上(通过 GFS 管理[8])的方式来减少网络带宽的开销。GFS 把文件分成 64M 一块,并且每
一块都有几个拷贝(通常是 3 个拷贝),分布到不同的机器上。MapReduce 的 master 有输入文件
组的位置信息,并且尝试分派 map 任务在对应包含了相关输入数据块的设备上执行。如果不能分
配 map 任务到对应其输入数据的机器上执行,他就尝试分配 map 任务到尽量靠近这个任务的输
入数据库的机器上执行(比如,分配到一个和包含输入数据块在一个 switch 网段的 worker 机器上
执行)。 当在一个足够大的 cluster 集群上运行大型 MapReduce 操作的时候,大部分输入数据都是
在本地机器读取的,他们消耗比较少的网络带宽。
3.5 任务颗粒度
第6页
(这样,上面描写的本地优化策略会最有效),另外,我们使 R 比较小,这样使得 R 占用不多的
worker 机器。我们通常会用这样的比例来执行 MapReduce: M=200,000,R=5,000,使用
2,000 台 worker 机器。
3.6 备用任务
4 技巧
虽然简单写 map 和 reduce 函数实现基本功能就已经对大部分需要都足够了,我们还是开发了一
些有用的扩展,这些在本节详细描述。
4.1 分区函数
第7页
输出是写到中间文件,并且会被发送到 reduce 任务中去。
部分使用 combiner 函数可以显著提高某些类型的 MapReduce 操作。附录 A 有这样的使用
combiner 的例子。
4.4 输入和输出类型
MapReduce 函数库提供了读取几种不同格式的输入的支持。例如,”text”模式下,每行输入都被看
成一个 key/value 对:key 是在文件的偏移量,value 是行的内容。另一个宠用格式保存了根据 key
进行排序 key/value 对的顺序。每一个输入类型的实现都知道如何把输入为了分别得 map 任务而进
行有效分隔(比如,text 模式下的分隔就是要确保分隔的边界只能按照行来进行分隔)。用户可以
通过简单的提供 reader 接口来进行新的输入类型的支持。不过大部分用户都只用一小部分预先定
义的输入类型。
reader 函数不需要提供从文件读取数据。例如,我们很容易定义一个 reader 函数从数据库读取数
据,或者从保存在内存中的数据结构中读取数据。
类似的,我们提供了一组用于输出的类型,可以产生不同格式的数据,并且用户也可以很简单的
增加新的输出类型。
4.5 边界效应
因为实际执行操作时分布在系统中执行的,通常是在好几千台计算机上执行得,并且是由 master
机器进行动态调度的任务,所以对 map 和 reduce 函数的调试就比较麻烦。为了能够让调试方便,
profiling 和小规模测试,我们开发了一套 MapReduce 的本地实现,也就是说,MapReduce 函数
库在本地机器上顺序执行所有的 MapReduce 操作。用户可以控制执行,这样计算可以限制到特定
的 map 任务上。用户可以通过设定特别的标志来执行他们的程序,同时也可以很容易的使用调试
和测试工具(比如 gdb)等等。
4.8 状态信息
第8页
处理百分比,等等。这些页面也包括了指向每个任务输出的标准错误和输出的标准文件的连接。用
户可以根据这些数据来预测计算需要大约执行多长时间,是否需要为这个计算增加额外的计算资
源。这些页面也可以用来分析为何计算执行的会比预期的慢。
此外,最上层的状态页面也显示了哪些 worker 失效了,以及他们失效的时候上面运行的 map 和
reduce 任务。这些信息对于调试用户代码中的 bug 很有帮助。
4.9 计数器
MapReduce 函数库提供了用于统计不同事件发生次数的计数器。比如,用户可能想统计所有已经
索引的 German 文档数量或者已经处理了多少单词的数量,等等。
为了使用这样的特性,用户代码创建一个叫做 counter 的对象,并且在 map 和 reduce 函数中在适
当的时候增加 counter 的值。例如:
Counter* uppercase;
uppercase = GetCounter("uppercase");
5 性能
在本节,我们用在一个大型集群上运行的两个计算来衡量 MapReduce 的性能。一个计算用来在一
个大概 1TB 的数据中查找特定的匹配串。另一个计算排序大概 1TB 的数据。
这两个程序代表了大量的用 MapReduce 实现的真实的程序的主要类型-一类是对数据进行洗牌,
另一类是从海量数据集中抽取少部分的关心的数据。
5.1 集群配置
第9页
的,树形交换网络中,在最上层大概有 100-200G 的聚合贷款。所有这些机器都有相同的部署(对
等部署),因此任意两点之间的来回时间小于 1 毫秒。
在 4GB 内存里,大概有 1-1.5G 用于运行在集群上的其他任务。这个程序是在周末下午执行的,这
时候的 CPU,磁盘和网络基本上属于空闲状态。
5.2 GREP
图 2 表示了这个程序随时间的处理过程。Y 轴是输入数据的处理速度。处理速度逐渐随着参与
MapReduce 计算的机器增加而增加,当 1764 台 worker 开始工作的时候,达到了 30G/s 的速度。
当 map 任务结束的时候,在计算开始后 80 秒,输入的速度降到 0。整个计算过程从开始到结束一
共花了大概 150 秒。这包括了大约一分钟的开头启动部分。开头的部分是用来把这个程序传播到各
个 worker 机器上的时间,并且等待 GFS 系统打开 100 个输入文件集合并且获得相关的文件位置
优化信息。
5.3 SORT 排序
第 10 页
图三是这个排序程序的正常执行过程。左上的图表示了输入数据读取的速度。数据读取速度会达到
13G/s,并且在不到 200 秒所有 map 任务完成之后迅速滑落到 0。我们注意到数据读取速度小于
grep 粒子。这是因为排序 map 任务划了大概一半时间和 I/O 带宽写入中间输出到本地硬盘。相对应
的 grep 中间结果输出几乎可以忽略不计。
左边中间的图是 map 任务把中间数据发送到 reduce 任务的网络速度。这个排序过程自从第一个任
务完成之后就开始了。图示上的第一个高峰是启动了第一批大概 1700 个 reduce 任务(整个
MapReduce 分布到大概 1700 台机器上,每台机器一次大概执行 1 个 reduce 任务)。大概计算开
始 300 秒以后,这些第一批 reduce 任务完成了,并且我们开始执行剩下的 reduce 任务。所有这些
排序任务会在计算开始后大概 600 秒结束。
左下的图表示 reduce 任务把排序后的数据写到最终的输出文件的速度。在第一个排序期结束后到
写盘开始之前有一个小延时,这是因为机器正在忙于内部排序中间数据。写盘速度持续大概 2-
4G/s。在计算开始后大概 850 秒左右写盘完成。包括启动部分,整个计算用了 891 秒。这个和
TeraSort benchmark[18]的最高纪录 1057 秒差不多。
需要注意的事情是:输入速度要比排序速度和输出速度快,这是因为我们本地化的优化策略,绝
大部分数据都是从本地硬盘读取而上去了我们相关的网络消耗。排序速度比输出速度快,这是因为
输出阶段写了两份排序后的速度(我们写两份的原因是为了可靠性可可用性的原因)。我们写两份
的原因是因为底层文件系统的可靠性和可用性的要求。如果底层文件系统用类似容错编码[14]
(erasure coding)的方式,而不采用复制写的方式,在写盘阶段可以降低网络带宽的要求。
5.4 高效的 backup 任务
第 11 页
5.5 失效的机器
6 经验
我们在 2003 年 1 月写了第一个版本的 MapReduce 函数库,并且在 2003 年 8 月作了显著的增强,
包括了本地优化,worker 机器之间的动态负载均衡等等。自那以后,MapReduce 函数库就广泛用
于我们日常处理的问题。它现在在 Google 内部各个领域内广泛应用,包括:
。大尺度的计算机学习问题。
。Google News 和 Froogle 产品的集群问题。
。从公众查询产品(比如 Google 的 Zeitgeist)的报告中抽取数据。
。从 web 网页作新试验和抽取新的产品(例如,从大量的 webpage 中的本地查找抽取物理位置信
息)。
。大尺度的图型计算。
第 12 页
任务数 29423
平均任务完成时间 634 秒
使用的机器时间 79,186 天
读取的输入数据 3,288TB
产生的中间数据 758TB
写出的输出数据 193TB
每个 job 平均 worker 机器数 157
每个 job 平均死掉 work 数 1.2
每个 job 平均 map 任务 3,351
每个 job 平均 reduce 任务 55
map 唯一实现 395
reduce 的唯一实现 296
map/reduce 的 combiner 实现 426
表 1:MapReduce2004 年 8 月的执行情况
索引代码很简单,很小,很容易理解。因为对于容错的处理代码,分布以及并行处理代码都
通过 MapReduce 函数库封装了,所以索引代码很简单,很小,很容易理解。例如,当使用
MapReduce 函数库的时候,计算的代码行数从原来的 3800 行 C++代码一下减少到大概 700
行代码。
MapReduce 的函数库的性能已经非常好,所以我们可以把概念上不相关的计算步骤分开处
理,而不是混在一起以期减少处理次数。这使得我们容易改变索引处理方式。比如,我们对老
索引系统的一个小更改可能要好几个月的时间,但是在新系统内,只需要花几天时间就可以
了。
索引系统的操作更容易了,这是因为机器的失效,速度慢的机器,以及网络风暴都已经由
MapReduce 自己解决了,而不需要操作人员的交互。此外,我们可以简单的通过对索引系统
增加机器的方式提高处理性能。
7 相关工作
很多系统都提供了严格的编程模式,并且通过对编程的严格限制来实现自动的并行计算。例如,一
个结合函数可以在一个 N 个元素的所有前缀上进行计算,并且使用并发前缀计算,会在在 N 个并
发节点上会耗费 log N 的时间[6,9,13]。MapReduce 是这些模式下的,一个我们基于超大系统
的现实经验的一个简化和精炼。并且,我们还提供了基于上千台处理器的容错实现。而大部分并发
第 13 页
处理系统都只在小规模的尺度上实现,并且机器的容错还是程序员来操心的。
Bulk Synchronous Programming[17]以及一些 MPI primitives[11]提供了更高级别的抽象,可以更容
易写出并行处理的程序。这些系统和 MapReduce 系统的不同之处在于,MapReduce 是通过限制
性编程模式自动实现用户程序的并发处理,并且提供了透明的容错处理。
我们本地的优化策略是受 active disks[12,15]等技术的影响的,在 active disks 中,计算任务是尽
量推送到数据在本地磁盘的节点处理,这样就减少了网络系统的 I/O 吞吐。我们是在直接附带几个
硬盘的通机器上执行我们的计算工作,不是在磁盘处理器上执行我们的工作,但是总的效果是一
样的。
我们的 backup task 机制和早先 CharlotteSystem[3]的机制比较类似。早先的简单调度的一个缺点
是如果一个任务导致反复失效,那么整个计算就不能完成。我们通过在故障情况下跳过故障记录的
方式,在某种程度上解决了这个问题。
MapReduce 的实现依赖于一个内部的集群管理系统,这个集群管理系统负责在一个超大共享机器
组上分布和运行用户任务。 虽然这个不是本论文的重点,集群管理系统在理念上和 Condor[16]等其
他系统一样。
MapReduce 函数库的排序部分和 NOW-Sort[1]的操作上很类似。源机器(map workers)把待排序
的数据进行分区,并且发送到 R 个 reduce worker 中的一个进行处理。每一个 reduce worker 作本
地排序(尽可能在内存排序)。当然 NOW-Sort 没有刻意用户定义的 Map 和 Reduce 函数,而我
们的函数库有,所以我们的函数库可以有很高的适应性。
River[2]提供了一个编程模式,在这样的编程模式下,处理进程可以通过分布式查询来互相传送数
据的方式进行通讯。和 MapReduce 类似,River 系统尝试提供对不同应用有近似平均的性能,即
使在不对等的硬件环境下或者在系统颠簸的情况下也能提供近似平均的性能。River 是通过精心调
度硬盘和网络的通讯,来平衡任务的完成时间。MapReduce 的框架是通过限制性编程模式,来把
问题分解成为大量的任务。每一个任务都是动态调度到可用的 worker 上执行,这样快速的 worker
可以执行更多的任务。限制性编程模式同样允许我们在接近计算完成的时候调度 backup 任务,在
出现处理不均匀的情况下,大量的缩小整个完成的时间(比如在有慢机或者阻塞的 worker 的时
候)。
BAD-FS[5]和 MapReduce 的编程模式完全不同,它不像 MapReduce 是基于很大的网络计算的。
不过,这两个系统有两个基本原理很类似。(1)两个系统都使用重复执行来防止由于失效导致的
数据丢失。(2)两个都使用数据本地化调度策略,使得处理尽可能在本地数据上进行,减少通过
网络通讯的数据量。
TACC[7]是一个用于简单构造高可用性网络服务的系统。就像 MapReduce,它依靠重新执行机制
来实现的容错处理。
8 结束语
MapReduce 的编程模式在 Google 成功应用于许多方面。我们把这种成功应用归结为几个方面:
首先,这个编程模式易于使用,即使程序员没有并行或者分布式系统经验,由于 MapReduce 封
装了并行的细节和容错处理,本地化计算,负载均衡等等,所以,使得编程非常容易。其次,大量
不同的问题都可以简单通过 MapReduce 来解决。例如,MapReduce 用于产生 Google 的 web 搜
索服务所需要的数据,用来排序,用来数据挖掘,用于机器智能学习,以及很多其他系统。第三,
我们已经在一个好几千台计算机的大型集群上开发实现了这个 MapReduce。这个实现使得对于这
些机器资源的利用非常简单,并且因此也适用于解决 Google 遇到的其他很多需要大量计算的问
题。
我们也从 MapReduce 上学到了不少内容。首先,先执行编程模式使得并行和分布式计算非常容易
并且也易于构造这样的容错计算环境。其次,网络带宽是系统的资源的瓶颈。我们系统的一系列优
化都使因此针对减少网络传输量为目的的:本地优化使得我们读取数据时,是从本地磁盘读取的,
并且写出单个中间数据文件到本地磁盘也节约了网络带宽。第三,冗余执行可以减少慢机器带来的
影响,并且解决由于机器失效导致的数据丢失问题。
第 14 页
9 感谢
Josh Levenberg 校定和扩展了用户级别的 MapReduce API,并且结合他的适用经验和其他人的改
进建议,增加了很多新的功能。MapReduce 使用 Google 文件系统 GFS[8]来作为数据和输出。我们
还感谢 Percy Liang Olcan Sercinoglu 在开发用于 MapReduce 的集群管理系统得工作。Mike
Burrows,Wilson Hsieh,Josh Levenberg,Sharon Perl,RobPike,Debby Wallach 为本论文提出了宝贵
的意见。OSDI 的无名审阅者,以及我们的审核者 Eric Brewer,在论文应当如何改进方面给出了有
益的意见。最后,我们感谢 Google 的工程部的所有 MapReduce 的用户,感谢他们提供了有用的
反馈,以及建议,以及错误报告等等。
10 参考资料
[1] Andrea C. Arpaci-Dusseau, Remzi H. Arpaci-Dusseau,David E. Culler, Joseph M. Hellerstein,
and David A. Patterson.High-performance sorting on networks of workstations.In
Proceedings of the 1997 ACM SIGMOD InternationalConference on Management of
Data, Tucson,Arizona, May 1997.
[2] Remzi H. Arpaci-Dusseau, Eric Anderson, NoahTreuhaft, David E. Culler, Joseph M.
Hellerstein, David Patterson, and Kathy Yelick. Cluster I/O with River:Making the fast
case common. In Proceedings of the Sixth Workshop on Input/Output in Parallel and
Distributed Systems (IOPADS '99), pages 10.22, Atlanta, Georgia, May 1999.
[3] Arash Baratloo, Mehmet Karaul, Zvi Kedem, and Peter Wyckoff. Charlotte: Metacomputing on
the web. In Proceedings of the 9th International Conference on Parallel and Distributed
Computing Systems, 1996. [4] Luiz A. Barroso, Jeffrey Dean, and Urs H¨olzle. Web
search for a planet: The Google cluster architecture. IEEE Micro, 23(2):22.28, April 2003.
[5] John Bent, Douglas Thain, Andrea C.Arpaci-Dusseau, Remzi H. Arpaci-Dusseau, and Miron
Livny. Explicit control in a batch-aware distributed file system. In Proceedings of the 1st
USENIX Symposium on Networked Systems Design and Implementation NSDI, March
2004.
[6] Guy E. Blelloch. Scans as primitive parallel operations.IEEE Transactions on Computers, C-
38(11), November 1989.
[7] Armando Fox, Steven D. Gribble, Yatin Chawathe, Eric A. Brewer, and Paul Gauthier. Cluster-
based scalable network services. In Proceedings of the 16th ACM Symposium on
Operating System Principles, pages 78. 91, Saint-Malo, France, 1997.
[8] Sanjay Ghemawat, Howard Gobioff, and Shun-Tak Leung. The Google file system. In 19th
Symposium on Operating Systems Principles, pages 29.43, Lake George, New York,
2003. To appear in OSDI 2004 12
[9] S. Gorlatch. Systematic efficient parallelization of scan and other list homomorphisms. In L.
Bouge, P. Fraigniaud, A. Mignotte, and Y. Robert, editors, Euro-Par'96. Parallel
Processing, Lecture Notes in Computer Science 1124, pages 401.408. Springer-Verlag,
1996.
[10] Jim Gray. Sort benchmark home page. http://research.microsoft.com/barc/SortBenchmark/.
[11] William Gropp, Ewing Lusk, and Anthony Skjellum. Using MPI: Portable Parallel Programming
with the Message-Passing Interface. MIT Press, Cambridge, MA, 1999.
[12] L. Huston, R. Sukthankar, R.Wickremesinghe, M. Satyanarayanan, G. R. Ganger, E. Riedel,
and A. Ailamaki. Diamond: A storage architecture for early discard in interactive search.
In Proceedings of the 2004 USENIX File and Storage Technologies FAST Conference,
April 2004.
[13] Richard E. Ladner and Michael J. Fischer. Parallel prefix computation. Journal of the ACM,
27(4):831.838, 1980. [14] Michael O. Rabin. Efficient dispersal of information for security,
第 15 页
load balancing and fault tolerance. Journal of the ACM, 36(2):335.348, 1989.
[15] Erik Riedel, Christos Faloutsos, Garth A. Gibson, and David Nagle. Active disks for large-
scale data processing. IEEE Computer, pages 68.74, June 2001.
[16] Douglas Thain, Todd Tannenbaum, and Miron Livny. Distributed computing in practice: The
Condor experience. Concurrency and Computation: Practice and Experience, 2004.
[17] L. G. Valiant. A bridging model for parallel computation. Communications of the ACM,
33(8):103.111, 1997.
[18] Jim Wyllie. Spsort: How to sort a terabyte quickly.
http://alme1.almaden.ibm.com/cs/spsort.pdf.
A 单词频率统计
本节包含了一个完整的程序,用于统计在一组命令行指定的输入文件中,每一个不同的单词出现
频率。
#include "mapreduce/mapreduce.h"
REGISTER_MAPPER(WordCounter);
第 16 页
};
REGISTER_REDUCER(Adder);
MapReduceSpecification spec;
// Now run it
MapReduceResult result;
if (!MapReduce(spec, &result)) abort();
第 17 页
B 译者
所有的版权归于原作者。
感谢:朱朱,洋洋,sophia
第 18 页