MapReduce论文总结

MapReduce

MapReduce解决了什么

早期谷歌实现了许多种计算过程,例如处理大量的原始数据,计算许多种类的衍生数据等。这些计算过程大都数据数据量非常大,因此计算过程需要分布到数百台或数千台机器上进行,才能保证过程在一个合理时间内结束,而为了处理计算并行化、数据分发和错误处理通常代码都非常复杂。为了解决这一过程,设计了一种新的抽象,将涉及并行,容错性,数据分发和负载均衡的细节包装在一个库里,用户只需要编写简单的的Map函数和Reduce函数就可以轻松的将大量计算并行化,并具有容错性。

简单来说,MapReduce是用于编写能够运行在普通机器上的大规模并行处理数据而抽象出来的编程模型,解决多机并行协同,网络通信,处理错误,提高效率等通用性问题的一个编程框架。

应用场景

分布式Grep: map函数在匹配到给定的pattern时输出一行。reduce函数只是将给定的中间数据复制到输出上。

URL访问频次统计: map函数处理网页请求的日志,对每个URL输出〈URL, 1〉。reduce函数将相同URL的所有值相加并输出〈URL, 总次数〉对。

倒转Web链接图: map函数在source页面中针对每个指向target的链接都输出一个〈target, source〉对。reduce函数将与某个给定的target相关联的所有source链接合并为一个列表,并输出〈target, list(source)〉对。

每个主机的关键词向量: 关键词向量是对出现在一个文档或一组文档中的最重要的单词的概要,其形式为〈单词, 频率〉对。map函数针对每个输入文档(其主机名可从文档URL中提取到)输出一个〈主机名, 关键词向量〉对。给定主机的所有文档的关键词向量都被传递给reduce函数。reduce函数将这些关键词向量相加,去掉其中频率最低的关键词,然后输出最终的〈主机名, 关键词向量〉对。

倒排索引: map函数解析每个文档,并输出一系列〈单词, 文档ID〉对。reduce函数接受给定单词的所有中间对,将它们按文档ID排序,再输出〈单词, list(文档ID)〉对。所有输出对的集合组成了一个简单的倒排索引。用户可以很轻松的扩展这个过程来跟踪单词的位置。

分布式排序: map函数从每条记录中提取出key,并输出〈key, 记录〉对。reduce函数不改变这些中间对,直接输出。这个过程依赖于4.1节介绍的划分机制和4.2节介绍的排序性质。

编程模型

计算过程就是一组key/value对,再生成一组key/value对。MapReduce库的使用者用两个函数表示这个过程:MapReduce

Map由使用者编写,使用一个输入key/value对,生成一组中间key/value对。MapReduce库将有相同key的中间value组合在一起,再传给Reduce函数

Reduce函数也由使用者编写,接收一个中间key I 和一组与 I 对应的value。它将这些value合并成一个可能更小的value集合。通常每个Reduce调用只产生0或1个输出value。中间value是通过一个迭代器提供给Reduce函数。这允许我们操作那些大到找不到连续内存存放而使用链表的value集合。

实现

执行过程

在这里插入图片描述

  1. 用户程序中的MapReduce库首先将输入文件切分成M块,每块大小从16MB到64MB(用户可通过一个可选参数控制此大小)。然后MapReduce库会在一个集群的若干机器上启用程序的多个副本。
  2. 程序的各个副本中有一个主节点,其他则是工作节点。主节点将M个Map任务分配给空闲的工作节点,每个节点一项任务。
  3. 被分配map任务的工作节点读取对应的输入区块内容。它从输入数据中解析出key/value对,然后将每个对传递给用户定义的map函数。由map函数产生的中间key/value对都缓存在内存中。
  4. 缓存的数据对会被周期性的由划分函数分成R块,并写入本地磁盘中。这些缓存对在本地磁盘中的位置会被传回给主节点,主节点负责将这些位置再传给reduce工作节点。
  5. 当一个reduce节点得到了主节点的这些位置通知后,它使用RPC调用去读map工作节点的本地磁盘中的缓存数据。当reduce工作节点读取完所有中间数据,它将这些数据按中间key排序,这样相同key的数据就被排列在一起了。同一个reduce任务经常会分到有着不同key的数据,因此这些排序很有必要。如果中间数据数量过多,不能全部载入内存,就会使用外部排序。
  6. reduce工作节点遍历排序好的中间数据,并将遇到的每个中间key和与它关联的一组中间value传递给用户的reduce函数。reduce函数的输出会被写到由reduce划分过程划分出来的最终输出文件的末尾(GFS)。
  7. 当所有的map和reduce任务都完成之后,主节点唤醒用户程序。此时,用户程序中的MapReduce调用返回到用户代码中。

成功完成后,MapReduce执行的输出都在R个输出文件中(每个reduce任务产生一个,文件名由用户指定)。通常不用合并这R个输出文件,因为这些文件经常被用作另一个MapReduce调用的输入,或者用作另一个可以处理分成多个文件输入的分布式应用。

数据结构

主节点维持多种数据结构。它会存储每个map和reduce任务的状态(空闲,处理,完成),和每台工作机器的ID(对应非空闲的任务)。由于主节点需要将map任务产生的中间文件的位置传递给reduce任务,因此,主节点还需要存储每个完成map任务产生的R个中间文件的位置和大小。位置和大小信息的更新情况会在map任务完成时接收到,这些信息会被逐步发送到正在处理中的reduce任务节点处。

容错

工作节点错误

主节点周期性的ping每个工作节点。如果工作节点在一定时间内没有回应,主节点就将它标记为已失败。这个工作节点完成的任何map任务都被重置为空闲状态,并可以被调度到其他工作节点上。同样的,失败的工作节点上正在处理的的map任务和reduce任务也被重置为空闲状态,允许被调度

失败节点上已完成的map任务需要重新执行的原因是因为它们的输出存储在失败机器的本地磁盘上,因此无法被访问到。而已完成的reduce任务不需要重新执行,因为它们的输出存储在一个全球文件系统上(GFS)。

当一个map任务先被A节点执行过,随后又被B节点重新执行(A节点失败),所有执行reduce任务的工作节点都会收到重新执行的通知。任何没有读取完A节点数据的reduce任务都会从B节点读取数据。

MapReduce可以弹性应对大范围的工作节点失败。MapReduce主节点会简单的重新执行由无法访问的机器完成的任务,并继续向前执行,最终完成这次MapReduce操作

主节点错误

主节点定期将数据结构保存为恢复点。如果主节点失败,会中止MapReduce计算,然后用户可以重新启动MapReduce操作,从上一个恢复点重新执行任务。

持久化容错

对于所有持久化操作,不可避免会有副作用,比如写数据写一半,然后任务失败了,重新写数据,会导致数据冗余写入,或者比如误判任务失败,但是任务没有失败,主节点又拉了一个新任务执行,因此同时存在两个任务写一个文件。

导致这种副作用的原因是不能原子的提交。解决办法就是map和reduce任务的输出先写入一个私有的临时文件中。当map任务完成时,工作节点发送给主节点的消息中带有R个临时文件的名字,如果主节点收到了一个已经完成节点的完成消息,就忽略它,否则,主节点会将这R个文件名字记录在相应的数据结构中。

当reduce任务完成时,工作节点会执行原子性的更名操作,将临时输出文件更名为最终输出文件。如果有多个相同的reduce任务在多个机器上执行,那么就会有多个更名调用在相同的最终输出文件上,依赖于底层文件系统提供的原子更名操作,能够保证最终只有一个reduce产生的数据。

备用任务

导致MapReduce操作用时比较长的一个常见原因是出现“落后者”:某台机器执行Map任务或者Reduce任务花费了比较长的时间。解决办法是在MapReduce执行快结束的时候,主节点会将仍在处理中的剩余任务调度给其他机器备用执行。原本执行和备用执行任何一个执行完毕都会将对应的任务标记为已完成。

有用的技巧

划分函数

MapReduce用户指定想要的reduce任务/输出文件的数量。通过划分函数可以将数据安中间key划分给各个reduce任务。默认使用Hash函数当作默认的划分函数(例如,“hash(key) mod R”)。当然,也可以指定自己的Hash函数。

顺序保证

保证在给定的划分中,中间的key/value对是按增序排列的,会保证每个划分产生一个有序的输出文件。

合并函数

允许用户指定一个可选的合并函数,在数据被发送之前进行局部合并,合并函数是由每个执行map任务的机器调用。通常合并函数和reduce函数是一样的。唯一的区别是合并函数会写到中间文件,并在随后发送给一个reduce任务,而reduce函数的输出会写到最终的输出文件中。

输入和输出类型

MapReduce库支持多种不同格式输出数据的读取。用户可以实现一个简单的Reader接口来提供对新的数据类型的支持,当然也不仅仅可以从文件中读取数据,也可以从数据库或者映射在内存中的某种数据结构中读取数据。

类似,既然支持了一组输入类型来产生不同类型的数据,而用户支持新的输出类型也不难。

略过坏记录

有时候用户代码的bug会导致map或reduce函数一遇到特定的记录就崩溃,MapReduce支持一个可选模式,在检测到确定会崩溃的记录就略过它们从而继续执行。

每个工作进程都需要设置一个信号处理程序来捕捉内存段异常(segmentation violation)和总线错误(bus error)。在调用map或者reduce函数之前,MapReduce库通过全局变量保存记录序号。如果用户程序触发了一个系统信号,信号处理函数就通过UDP包向主节点发送处理的最后一条记录的序号。当主节点看到某个特定记录不止失败一次的时候,就标志着某条记录需要被跳过,并在下次重新执行相关的Map或者Reduce任务时跳过这条记录。

计数器

用户程序可以创建一个有名的计数器对象,并在map和reduce函数的适当位置增加它的值。

Counter *uppercase = GetCounter("uppercase");
    
map(String name, String contents):
    for each word w in contents:
        if (IsCapitalized(w)):
            uppercase->Increment();
        EmitIntermediate(w, "1");
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值