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

3实现


MapReduce模型可以有多种不同的实现方式。如何正确选择取决于具体环境。例如某种实现可能适用于一台小型共享内存型机器,另一种实现方式则适用于大型NUMA架构的多核处理器机器上。然而,有的实现方式可能更适合大型的基于网络的机器集群。

本节所介绍的是一个针对在谷歌内部所广泛使用的计算环境下使用的实现:通过以太网交换机连接,并由商用服务器所组成的大型集群。我们的环境配置如下:

1.x86架构,Linux系统,双处理器,每台机器的内存为2-4GB

2.商用网络硬件——通常它们的网速为100Mbit/s或者是1000Mbit/s, 但是远小于网络的平均带宽的一半。

3.集群由成百上千台机器所组成,因此,机器故障是常有的事情。

4.存储设备则是廉价的IDE硬盘。通过一个内部的分布式文件管理系统来管理这些硬盘上的数据。该文件系统通过使用数据复制来在不可靠的硬件上保证数据的可用性和有效性。

5.用户提交工作给调度系统。每项工作包含了一系列任务,调度系统将这些任务调度到集群中多台可用的机器上来进行。

3.1 执行概述


通过将传入Map函数的输入数据自动切分为M个数据片段的集合,这样就能将Map操作分布到多台机器上运行。输入数据的片段可以在不同的机器上进行并行处理。使用分区函数将Map函数所生成的中间key值分成R个不同分区(例如,hash(key) mod R),这样就可以将Reduce操作也分布到多台机器上并行处理。分区数量R和分区函数则是由用户指定。

在这里插入图片描述

Figure 1展示了我们所实现的MapReduce操作的整体工作流程。当用户程序调用MapReduce函数时,将会发生下面一系列的动作(下面的序号与图中的序号一一对应)。

1.用户程序中的MapReduce库会先将输入文件切分为M个片段,通常每个片段的大小在16MB到64MB之间(具体大小可以由用户通过可选参数来进行指定)。接着,它会在集群中启动许多个程序副本。

2.有一个程序副本是比较特殊的,那就是master。剩下的副本都是worker,master会对这些worker进行任务分配。这里有M个Map任务以及R个Reduce任务要进行分配。master会给每个空闲的worker分配一个map任务或者一个reduce任务。

3.被分配了map任务的worker会读取相关的输入数据片段。它会从输入数据中解析出键值对,并将它们传入用户定义的Map函数中。Map函数所生成的中间键值对会被缓存在内存中(知秋注:用户自定义的map函数只是中间的一环而已,我们其实可以将这个map看作map(K,V,BiFunction<K,V,Tuple<K1,V1>>) K是文件名,V是文件内容,BiFunction就是我们自己定义的map规则)。

4.每隔一段时间,被缓存的键值对会被写入到本地硬盘,并通过分区函数分到R个区域内。这些被缓存的键值对在本地磁盘的位置会被传回master。master负责将这些位置转发给执行reduce操作的worker。

5.当master将这些位置告诉了某个执行reduce的worker,该worker就会使用RPC的方式去从保存了这些缓存数据的map worker的本地磁盘中读取数据。当一个reduce worker读取完了所有的中间数据后,它就会根据中间键进行排序,这样使得具有相同键值的数据可以聚合在一起。之所以需要排序是因为通常许多不同的key会映射到同一个reduce任务中。如果中间数据的数量太过庞大而无法放在内存中,那就需要使用外部排序。

6.reduce worker会对排序后的中间数据进行遍历。然后,对于遇到的每个唯一的中间键,reduce worker会将该key和对应的中间value的集合传入用户所提供的Reduce函数中。Reduce函数生成的输出会被追加到这个reduce分区的输出文件中。

7.当所有的map任务和reduce任务完成后,master会唤醒用户程序。此时,用户程序会结束对MapReduce的调用。

在成功完成任务后,MapReduce的输出结果会存放在R个输出文件中(每个reduce任务都会生成对应的文件,文件名由用户指定)。一般情况下,用户无需将这些文件合并为一个文件。他们通常会将这些文件作为输入传入另一个MapReduce调用中。或者在另一个可以处理这些多个分割文件的分布式应用中使用。

3.2 Master的数据结构

在Master中包含了一些数据结构。它保存了每个Map任务和每个Reduce任务的状态(闲置,正在运行,以及完成),以及非空闲任务的worker机器的ID。

master就像是一个喷泉(知秋注:管理了一堆喷口,数据准备好就喷到需要接收的地方),它将map任务所生成的中间文件区域的位置传播给reduce任务。故,对于每个完成的map任务,master会保存由map任务所生成的R个中间文件区域的位置和大小。当map任务完成后,会对该位置和数据大小信息进行更新。这些信息会被逐渐递增地推送给那些正在运行的Reduce工作。

3.3 容错

因为MapReduce库的设计旨在使用成百上千台机器来处理海量的数据,所以该库必须能很好地处理机器故障

Worker故障

master会周期性ping下每个worker。如果在一定时间内无法收到来自某个worker的响应,那么master就会将该worker标记为failed。所有由该worker完成的Map任务都会被重设为初始的空闲(idle)状态。因此,之后这些任务就可以安排给其他的worker去完成。类似的,在一台故障的worker上正在执行的任何Map任务或者Reduce任务也会被设置为空闲状态,并等待重新调度。

当worker故障时,由于已经完成的Map任务的输出结果已经保存在该worker的硬盘中了,并且该worker已经无法访问,所以该输出也无法访问。因此,该任务必须重新执行。然而,已经完成的Reduce任务则无需再执行,因为它们的输出结果已经存储在全局文件系统中了。

当一个Map任务由worker A先执行,但因为worker A故障了,之后交由worker B来执行。所有执行Reduce任务的woker就会接受到这个重新执行的通知。任何还没有从worker A中读取数据的Reduce任务将从worker B中读取数据。

MapReduce能够处理大规模worker故障。例如,在一次MapReduce操作期间,在某个正在运行的集群上进行网络维护会导致80台机器在几分钟中无法访问。MapReduce的master只需要简单地将这些由不可访问的worker机器所完成的任务重新执行一遍即可。之后继续执行未完成的任务,直到最后完成这个MapReduce操作

Master故障

一个简单的解决好办法就是让master周期性的将上文所描述的数据结构写入磁盘,即checkpoint。如果这个master挂掉了,那么就可以从最新的checkpoint创建出一个新的备份,并启动master进程。然而,因为只有一个master,所以我们并不希望它发生故障。因此如果master故障了,我们目前的实现会中断MapReduce计算。客户端可以检查该master的状态,并且根据需要可以重新执行MapReduce操作。

出现故障时的语义(semantics in the presence of failures)

当用户提供的map和reduce运算符是确定性函数时,我们所实现的分布式系统在任何情况下的输出都和所有程序在没有任何错误、并且按照顺序生成的输出是一样的。

我们依赖于map任务和 reduce任务输出的原子性提交来实现这个特性。每个正在执行的任务会将它的输出写入到私有的临时文件中去。每个Reduce任务会生成这样一个文件,每个Map任务则会生成R个这样的文件(一个Reduce任务对应一个文件)。当一个map任务完成时,该map任务对应的worker会向master发送信息,该信息中包含了R个临时文件的名字。如果master从一个已经完成的map工作的worker处又收到这个完成信息,master就会将该信息忽略。否则,它会将这R个文件名记录在master的数据结构中。

当Reduce任务完成时,reduce worker会以原子的方式将临时输出文件重命名为最终输出文件。如果多台机器执行同一个reduce任务,那么对同一个输出文件会进行多次重命名。我们依赖于底层文件系统所提供的原子性重命名操作来保证最终的文件系统状态仅包含一个Reduce任务所产生的数据。

我们的map和reduce运算符绝大多数情况下是确定性的,在这种情况下我们的语义就代表了程序的执行顺序,这使得能够轻易地理解其程序的行为。当map 和reduce运算都是非确定性的情况下,我们会提供一种稍弱但依旧合理的语义。当在进行一个非确定性操作时,Reduce任务R1的输出等同于一个非确定性程序按顺序执行产生的输出。但是另一个Reduce任务R2的输出可能符合一个不同的非确定顺序程序执行产生的R2的输出。(知秋注:输出的结果可以由A来处理,也可以由B来处理,比如A处理{a,b,c},B处理{a,d,e},现在map下发了一个a,那这个a既可以交由A,也可以交由B进行处理,又好比编译原理中的词法分析,if可以被identify处理,也可以被keywords处理,只不过我们在其中设定了优先级,那弱语义就变为了强语义)

考虑下这种情况,我们有一个Map任务M,两个Reduce任务,R1和R2。假设e(Ri)是Ri已经提交的执行过程(此处的e代表execution)(有且只有这样一次的提交)。当e(R1)已经读取了由M产生的一次输出,并且e(R2)读取了由M产生的另一次输出,这就会导致较弱语义的发生(知秋注:结合上一个注,如果map下发了两次a,第一次A处理了,第二次B处理了,这就是所谓的较弱语义的发生)。

3.4 地区性

在我们的计算环境中,网络带宽是一个相当稀缺的资源。我们尽量将输入数据(由GFS系统管理)存储在集群中机器的本地硬盘上,以此来节省网络带宽。GFS将每个文件分割为许多64MB大小的区块(Block),并且会对每个区块保存多个副本(分散在不同的机器上,通常是3个副本)。MapReduce的master在调度Map任务时会考虑输入数据文件的位置信息。尽量在包含该相关输入数据的拷贝的机器上执行Map任务。如果任务失败,master会尝试在保存输入数据副本的邻近机器上执行Map任务(例如,将任务交由与包含数据的机器在同一网络交换机下的woker机器去执行)。当一个集群中大部分worker机器都在执行MapReduce操作时,大部分输入数据会在本地进行读取,这样就不会消耗网络带宽。

3.5 任务粒度

如上所述,我们将map任务拆分成M个子任务来做,并且将reduce任务也拆分成R个子任务来做。理想情况下,M和R应该远大于worker机器的数量。每个worker都会执行许多不同的任务,以此来提升动态负载均衡的能力。并且,当一个worker故障了,这也能加速恢复的进度:即当机器故障时,许多由该故障机器所完成的任务可以快速分发到其他正常的worker上去执行。)

在我们的实现中,我们对M和R的值也做出了一定的限制。如上所述,因为master必须执行O(M+R)次调度,并且在内存中保存O(MR)个状态(这对于内存的使用率来说影响还是比较小的,在O(MR)个状态中,每个状态保存的每对Map任务/Reduce任务的数据大小为1 byte)。

此外,R的大小通常由用户所指定。因为每个reduce任务完成后,它的输出会保存在一个独立的输出文件中。在实际场景中,我们也会使用合适的M值,这样可以让每个map任务中的输入数据大小在16MB到64MB之间(这样对于上文所述的地区性优化而言,也是最为有效的)。我们将R设置为我们想要使用的worker机器数量的倍数。在MapReduce计算中,我们常用的M大小为200000,R为5000,用到的worker机器数量为2000。

3.6 备用任务

让MapReduce任务执行的总时间变长的一个常见原因就是落伍者的出现,即在MapReduce计算中,一台机器花费了异常长的时间去完成最后几个Map或者Reduce任务,这样导致整个计算时间延长。导致落伍者出现的情况有很多。例如,某台机器上的硬盘出现了问题,在读取的时候经常要对数据进行纠错,这就导致硬盘的读取性能从30MB/s降低为1MB/s。如果集群中的调度系统在这台机器上又分派了其他任务,由于CPU、内存,本地硬盘和网络带宽等竞争因素的存在,这会导致正在执行的MapReduce代码的执行速度更加缓慢。我们最近所遇到的一个问题是在机器初始化代码中的一个bug,它导致了处理器的缓存被禁用。在这种机器上运行MapReduce计算的效率要比正常机器低百倍。

我们有一个通用机制来降低落伍者导致的这种问题所带来的影响。当一个MapReduce计算接近完成时,master会调度一个备用(backup)任务来执行剩下的处于正在执行中(in-progress)的任务。无论是这个主任务还是这个备用任务完成了,我们都会将这个任务标记为完成。我们对这个机制进行了调优。通常情况下,它只会比正常操作多占几个百分点的计算资源。我们发现这样做能够显著减少运行大型MapReduce计算时所要花费的时间。例如,在章节5.3中的排序案例所述,如果这种备用机制被禁用,那我们将多花44%的时间来完成排序任务。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值