MapReduce:大型集群上的简单数据处理
摘要
MapReduce是一个编程模型和一个处理和生成大数据集的相关实现。用户指定一个map函数处理一个key-value对来生成一组中间key-value对;指定一个reduce函数合并所有和同一中间key值相联系的中间value值。许多现实世界中的任务以这个模型展现,就像文中展示的那样。
以这种函数类型编写的程序在一群日常机器上自动并行化并执行。运行时系统关心划分输入数据的细节,在一组机器间调度程序的执行,处理机器失效,管理内部机器需要的通信。这使得那些没有任何并行和分布式系统经验的程序员可以容易地使用大型分布式系统的资源。
我们MapReduce的实现运行在一大群日常机器上并且高度可扩张:一个典型的MapReduce计算在成千上万台机器上处理数TB的数据。程序员发现系统很易用:成百上千的MapReduce程序已经被实现,每天都有多余1000个MapReduce作业在Google集群上执行。
1.介绍
在过去的5年里,作者以及Google里的其他程序员已经实现了数以百计的,特殊目的的计算。这些计算处理海量原始数据,比如,文档抓取(shijin:类似网络爬虫的程序)、web请求日志等;或者计算各种各样的派生数据,比如倒排索引、web文档的图结构的各种表示形势、每台主机上网络爬虫抓取的页面数量的汇总、列举一天中一组最频繁的查询等。大多数这种计算概念上直截了当。然而输入的数据量巨大,并且为了在合理的时间内完成,计算不得不分布到数百或数千台机器上。如何并行计算、分发数据、处理失效的问题凑在一起使原本简单的计算晦涩难懂,需要大量复杂的代码来处理这些问题。
作为对上述复杂性的应对,我们设计一个新的抽象模型,其使我们可以表达我们试图执行的简单运算,但是将并行、容错、数据分布和负载均衡等散乱的细节隐藏在了一个库里面。我们抽象模型的灵感来自Lisp和许多其他函数式语言的map和reduce原语。我们意识到大多数我们的计算都涉及这样的操作:在我们输入中的每个逻辑“记录”上应用map操作,以便计算出一组中间key/value对,然后在所有享有相同key值的value值应用reduce操作,以便合适地合并派生的数据。我们使用带有用户指定map和reduce操作的函数模型,就可以轻易地并行化大规模计算;并且可以使用“再次执行”(re-execution)作为基础的容错机制。
这项工作的主要贡献是一个简单强大的接口,该接口使自动地并行化和分布大规模计算成为了可能,接口连同该接口的实现,实现了在大群日常PC机上的高性能。
第二节描述基本的编程模型给出一些例子。第三节描述了为我们基于集群的计算环境定做的MapReduce接口的实现。第四节描述一些我们发现有用的编程模型优化。第五节对我们各种不同任务实现的性能进行了测量。第六节探索了MapReduce在Google内部的使用,包含我们在使用其作为我们生产索引系统的重写操作的基础时的经验。第七节讨论相关的和未来的工作。
2.编程模型
计算取出一组输入key/value对,产生一组输出key/value对。使用MapReduce库的用户用两个函数表达这个计算:Map和Reduce。
用户自己编写的Map接受一个输入对,然后产生一组中间key/value对。MapReduce库把所有和相同中间key值I关联的中间value值聚集在一起后传递给Reduce函数。
也是由用户编写的Reduce函数接受一个中间key值I和一组那个key值的value值。Reduce函数将这些value值合并在一起,形成一个可能更小的value值的集合。通常每次Reduce调用仅产生0或1个输出value值。中间value值通过一个迭代器提供给用户的Reduce函数,这样我们就可以处理因太大而无法适应内存的value值列表。
2.1 例子
考虑这么一个问题,在一个大的文档集合中对每个单词出现的次数进行计数,用户可能要类似下面伪代码的代码:
map(String key, String value):
// key: document name
// value: documen t 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 value s:
result += Parse Int(v);
Emit(AsString(result));
Map函数emit每个词加上一个相关的出现计数(在这个简单的例子里就是1)。Reduce函数把为一个特定的词emit的所有计数加起来。
另外,用户编写代码用输入和输出文件的名字以及可选的调节参数填充一个mapreduce说明对象,用户之后调用MapReduce函数,并把这个说明对象传递给它。用户的代码和MapReduce库链接在一起(用C++实现)。附录A包含了这个例子的全部程序文本。
2.2 类型
尽管在前面的伪代码按照字符串输入输出书写,但是在概念上,用户提供的map和reduce函数有相关的类型:
map (k1,v1) ->list(k2,v2)
reduce (k2,list(v2)) ->list(v2)
比如,输入的key值和value值与输出的key值和value值从不同的类型域得到。并且,中间key值和value值与输出key值和value值来自同一个类型域。(alex注:原文中这个domain的含义不是很清楚,我参考Hadoop、KFS等实现,map和reduce都使用了泛型,因此,我把domain翻译成类型域)。
我们的C++实现使用字符串类型作为用户自定义函数的输入输出,并将字符串与适当类型的转换工作交给了客户代码。
2.3 更多的例子
这里有一些有趣程序的简单例子,可以很容易地作为MapReduce计算来表示:
分布式的Grep:如果与提供的模式串匹配,Map函数emit一行,Reduce函数是一个恒等函数,即仅仅把提供的中间数据复制到输出。
URL访问频率计数:Map函数处理web页面请求的日志,然后输出(URL,1)。Reduce函数把相同URL的value值加在一起,emit一个(URL,总数)对。
倒转网络链接图:Map函数为每个链接输出(target,source)对,每个链接连接到在名字叫源的页面中发现的目标URL。Reduce函数把与给定目标URL相关的所有源URL连接成列表,emit(target,list(source))对。
每个主机的检索词向量:检索词向量将一个文档或者一组文档中出现的嘴重要的词汇总为一个(词,频)对列表。Map函数为每个输入文档emit(主机名,检索词向量),其中主机名来自文档的URL。Reduce函数为一个给定主机接收所有每文档检索词向量,其将这些检索词向量加在一起,丢弃低频的检索词,然后emit一个最终的(主机名,检索词向量)对。
倒排索引:Map函数解析每个文档,emit一系列(词,文档号)列表,Reduce函数接受一个给定词的所有(词,文档号)对,排序相关的文档号,emit(词,list(文档号))。所有的输出对集合形成一个简单的倒排索引,增加计算来跟踪词在文档中的位置很简单。
分布式排序:Map函数从每个记录提取key值,emit(key,record)对。Reduce函数原封不动地emit所有对。这个运算依赖4.1描述的分区设备和4.2节描述的排序属性。
3.实现
MapReduce有多种不同的可能实现。正确的选择取决于环境。例如,一种实现可能适用于小型共享内存的机器,另外一种则适用于大型NUMA多处理器,然而还有的适合大型的网络集群。
本章节描述一个针对Google内部广泛使用的运算环境的实现:大群日常机器用交换以太网连接在一起。在我们环境中:
1. 机器通常是x86双核处理器、运行Linux系统、每台机器2-4GB内存。
2. 使用日常网络硬件,通常在机器级别带宽为百兆每分或者千兆每分,但是平均远小于网络整体带宽的一半。(averaging considerably less in overall bisection bandwidth)
3. 集群包含数百或数千台机器,因此机器