MapReduce:在大型集群上简化数据处理(3)

4 改进


虽然简单的Map和Reduce函数所提供的基本功能已经足以满足大部分的计算需要,但我们还是发现了一些有价值的扩展功能。本节将会对此进行介绍

4.1 分区函数

MapReduce的使用者通常会指定Reduce任务和Reduce输出文件的数量为R。在这些中间键上我们会使用一个分区函数来将这些数据进行分区。默认情况下,分区函数使用的是哈希进行取模(例如:hash(key) mod R)进行分区。这样能够生成非常均匀的数据分区。但是在某些情况下,通过向其他的一些分区函数传入key来进行分区会非常有用。比如,有时候输出的key为URL,我们希望每个主机的所有条目存放在同一个输出文件中。为了支持类似的情况,使用MapReduce库的用户可以提供一个特殊函数。例如,使用 “hash(Hostname(key)) mod R”作为分区函数,就可以把所有来自同一个主机的URL保存在同一个输出文件中。

4.2 顺序保证

在给定的分区中,我们保证中间键值对的处理顺序是根据key的大小进行升序排列。这种顺序保证在每个分区生成一个有序的输出文件。当输出文件格式需要支持根据key来进行有效的随机访问时,这种就很有用,或者当用户要对输出数据进行排序时,这也会很方便。

4.3 Combiner函数

//类似分布型的redece函数,先在worker本地端(map后)进行初步reduce

在某些情况下,在每个map任务所生成的中间key中会存在明显的重复数据,并且由用户提供的Reduce函数具备结合性以及交换性。在章节2.1处的字数统计程序就是一个很好的例子。由于单词的出现频率会趋向于一个Zipf分布(齐夫分布)。每个map任务会生成成百上千条 <the, 1>这样的形式的记录。所有这些记录会通过网络发送到一个reduce任务中,通过一个Reduce函数将它们加起来,并生成一个数字。我们允许用户提供一个可选的Combiner函数,在数据通过网络发送之前,可以通过该函数将数据进行部分合并。

Combiner函数会在每台执行Map任务的机器上执行一次。通常情况下,Combiner函数和Reduce函数的实现代码是一样的。Reduce函数和Combiner函数唯一的区别在于MapReduce库处理函数输出上面会有所不同。Reduce函数的输出会被写入一个最终输出文件中,而Combiner函数的输出会被写入一个要发送给reduce任务的中间文件中。

部分合并会明显提升某类MapReduce操作的速度。附录A中包含了使用combiner的一个例子。

4.4 输入和输出类型

MapReduce库支持多种不同格式的输入数据。例如,在文本模式下,每行的输入会被当做一个key/value对进行处理。它将该文件中的偏移量作为key,该行的内容作为value。另一种常见格式是以key来进行排序,以此来存储一系列key/value对的序列。每种输入类型实现只知道如何将自身拆分为有意义的范围,并以此作为独立的Map任务进行处理(例如,文本模式下的范围拆分必须在每行的边界处进行)。虽然大部分用户仅会去使用那些已经预先定义好的输入类型中的某一种,但是用户依然可以通过提供一个简单的reader接口的实现来对一种新的输入类型进行支持。

reader并非一定要从文件中读取数据。例如,我们可以简单的定义一个reader,让它从一个数据库或者映射在内存中的数据结构中读取数据。

类似的,我们也支持一组输出类型,以便生成不同格式的数据。并且在用户代码中添加对新输出格式的支持是很容易的。

4.5 副作用

在某些情况下,MapReduce的使用者发现通过他们的Map和Reduce操作额外生成的辅助文件能给他们带来一些便利。我们需要应用程序开发人员自己去实现一些功能来避免这种便利所带来副作用,即保证原子性和幂等性。通常情况下,应用程序会对一个临时文件进行写入,一旦该文件完全生成完毕,就会以原子的方式对该文件重命名。

对于一个任务产生了多个输出文件的这种情况,我们并没有为两段提交提供原子性的支持。因此,生成多个输出文件并且具有跨文件一致性需求的任务应当具有确定性。在实际使用过程中,目前这个限制还没有给我们带来任何问题。

4.6 跳过损坏的记录

有时在用户代码中存在了会引起Map或Reduce函数在处理某些特定记录时崩溃的bug。这种bug会妨碍MapReduce操作的完成。通常的做法就是修复bug,但有时这种方式并不可取。可能,这些bug是由第三方库所引起的,并且无法看到它们的源码。当然,有时忽略部分记录也是可以接受的。例如,在处理大型数据集的时候。我们提供了一种可选的执行模式,当MapReduce库检测到某些记录绝对会引起崩溃的时候,它就会跳过这些记录,以此来保证计算进度的推进。

每个worker进程都会通过一个handler来捕获内存段异常(segmentation violation)和总线错误(bus error)。在调用用户的Map或Reduce操作前,MapReduce库会用一个全局变量来保存参数序号。如果用户代码产生了一个signal,singnal handler就会在发送最后一个UDP包时,在该UDP包中放入该序号并发送给MapReduce master的序号。当master检测到在某条记录上有多次故障的时候,当它发出相应的Map或者Reduce任务重新执行时,它就表示应该跳过这条记录。

4.7 本地执行

对Map和Reduce函数中的bug进行调试是非常棘手的,因为实际计算是在一个分布式系统中进行的。通常需要在几千台机器上进行计算,并且任务是由master进行动态分配的。为了方便测试,分析,以及进行小规模测试,我们又开发了一版MapReduce库的实现。该库可以在本地计算机上顺序执行MapReduce操作的所有工作。用户可以控制MapReduce操作的执行,这样就可以将计算限制为特定的map任务。用户在调用他们的程序时设定特殊的标记,这样就能简单的使用他们觉得有用的debug工具或者测试工具(例如,gdb)。

4.8 状态信息

在master中,有一个内置的HTTP服务器,它可以用来展示一组状态信息页面。状态页面会显示计算进度,例如:已经完成的任务数量、正在执行的任务数量、输入的字节数、中间数据的字节数、输出的字节数、处理率等等。这些页面也包含了指向每个任务的标准差以及生成的标准输出文件的链接。用户可以使用这些数据来预测计算需要多久才能完成,是否需要往该计算中增加更多资源。当计算消耗的时间比预期时间更长的时候,这些页面也可以用来找出为什么执行速度很慢的原因。

此外,顶层的状态页面会显示那些故障的worker,以及它们故障时正在运行的Map和Reduce任务。这些信息对于调试用户代码中的bug很有帮助。

4.9 计数器

MapReduce库提供了计数器机制,它能用来统计不同活动的发生次数。例如,统计已经处理过的单词个数或者引用的德语文档的数量,等等。

为了使用这种机制,用户需要创建一个名为Counter的对象,然后在Map和Reduce函数中以正确的方式增加counter的数字。例如:

Count* uppercase;
uppercase = GetCounter("uppercase");

map(String name, String contents) :
for each word w in contents:
if(isCapitalized(w)):
uppercase->Increment();
EmitIntermediate(w,"1");

每隔一段时间,这些counter值会从每个worker机器传递给master(附加在ping的应答包中进行传递)。当MapReduce操作完成时,master会将这些已经成功完成的map和reduce任务中返回的counter的值聚合在一起,并将它们返回给用户代码。当前的counter值会显示在master的状态页面中。这样,人们就可以看到当前计算的进度。当聚合这些counter的值时,master会去掉那些重复执行的相同map或者reduce操作的次数,以此避免重复计数(之前提到的备用任务和故障后重新执行任务,这两种情况会导致相同的任务被多次执行)。

有些counter值是由MapReduce库自动维护的,例如已经处理过的输入键值对的数量以及生成的输出键值对的数量。

用户发现,计数器机制对于MapReduce操作行为的健壮性检查非常有用。例如,在某些MapReduce操作中,用户代码想确保输出的pair数量准确地等于已经处理过的pair数量,或者确保处理的德语文档数量在处理的整个文档数量的合理范围内。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值