MapReduce
MapReduce
论文《MapReduce: Simplified Data Processing on Large Clusters》笔记
Programming Model
概述
Map-Reduce是一种分布式系统的处理流程

按照论文图中所示,Map阶段实现的是对原始数据(raw data)的初步处理,可利于系统分发任务给reduce阶段。而在Reduce阶段,则从Intermediate files中读取数据(可理解为从任务列表中索取任务)并产生输出。
下面标注论文当中对Map、Reduce的解释
Map, written by the user, takes an input pair and produces a set of intermediate key/value pairs. The MapReduce library groups together all intermediate values associated with the same intermediate key I and passes them to the Reduce function
The Reduce function, also written by the user, accepts
an intermediate key I and a set of values for that key. It
merges together these values to form a possibly smaller
set of values. Typically just zero or one output value is
produced per Reduce invocation. The intermediate values are supplied to the user’s reduce function via an iterator. This allows us to handle lists of values that are too
large to fit in memory.
从概念上讲,Map主要负责从原始数据“提炼”出多个key-value对。同时MapReduce库还要负责针对key对这些键值对进行分组(产生(key,array value[])这样的数据,毕竟一个key可能对应复数个value),并传递给Reduce函数(传递key和value数组)
Reduce函数接受了来自Map的参数后,会将这些值进行合并,产生一个更小的数据集合。通常来说每次Reduce调用只产生0或者一个输出值。论文中强调了Reduce接受array value[]时接受的是迭代器,以获取超大占用value列表(废话)
示例
给出的一个经典例子便是对大文档内的单词进行计数。
map(String key, String value):
// key: document name
// value: document 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 values:
result += ParseInt(v);
Emit(AsString(result));
map函数中对文档内容的单词进行遍历,并发送(word,“1”)的中间键值对。我们知道Map-Reduce是并发执行的,并不是Map执行一次,Reduce执行一次,这样就成串行了
主要解释为什么Reduce会接受一个value列表参数,毕竟执行Reduce的时候,指定word已经不止被遍历到一遍了,那么它就会对应多个“1”。value参数列表由此而来。
Reduce接受到参数之后,就对value[]中的所有值进行求和,并发送出结果
在这里全部都是“1”,那
result += ParseInt(v);就相当于result++;了
额外示例
URL访问频率计数
Map函数可以通过处理web页面的请求日志来输出键值对<URL,1>,Reduce接受到后处理(URL,array value[])进行求和,发送<URL,total count>
Reverse Web-Link Graph(反向Web连接图)
通常来讲一个页面会引用其他页面(比如跳转链接),则该页面可成为source page,被引用的页面被称为target page。简历source page到target page的索引是很自然的事情,但同时也想记录target page到source的关系图。
因此Map函数可以为每一个在source page发现的target page输出<target,source>键值对。Reduce将所有与target URL关联的source连接起来,最终输出<target,list(source)>
Inverted Index(倒排索引)
Inverted Index主要是建立关键词对文档的索引以应用在搜索引擎中。
Map函数解析每一个文档,并且发送格式为<word, doucument ID>的序列。
Reduce函数接收到了指定单词所有键值对后,开始连接所有的doucumet ID,并发射形为<word, list(document ID)>键值对
Implementation
Execution Overview(执行概述)
首先数据会被划分,Map调用将被分发到多个机器上。输入数据的分割可以由不同的机器并行处理。通过使用分区函数(例如hash(key) mod R)来将中间键值对进行分块,然后被分发到多个Reduce调用上。分块数和分区函数都可以由用户指定。
用户程序调用MapReduce函数时,会发生以下一系列动作:
在用户程序中的MapReduce库会把输入文件分成 M 块,通常为16-64M不等(可以由用户指定)。然后在 一组 机器上启动该程序的许多副本(RPC调用)
Master单元(母单元)负责分配任务到工作单元,会选择空闲的工作单元去处理Map或Reduce任务。
被分配到map任务的工作单元读取相应的输入分块内容。它将解析数值对,并将每一个键值对传递给用户定义的Map函数(
map(key, value))。Map函数产生的中间键值对存放在内存缓冲区。周期性地将缓冲区内的键值对写入本地磁盘,并通过分区函数划分为R个区域。这些键值对在本地磁盘上的位置被传递给Master单元,Master负责将这些位置转发给reduce worker。
当Master单元体制Reduce单元这些位置时,将使用RPC调用从map worker的本地磁盘读取缓冲数据。当Reduce单元读取了所有的中间数据后,它将所有的中间键值对进行排序,以便将所有重复出现的相同键值对分组在一起。排序是必要的,因为通常有许多不同的键映射到相同的reduce任务。果中间数据量太大,内存无法容纳,则使用外部排序。
Reduce单元遍历已排序的中间数据,并且对于每一个唯一的“key”都会将key和相应的 intermediate values[]传递给用户定义的Reduce调用(
Reduce_function(key, value[]))。Reduce函数的输出将被附加到这个Reduce分区的最终输出文件中。当所有的map和reduce任务完成后,master单元会唤起user program.
此时,用户程序中的MapReduce调用返回到用户代码。
Master Data Structures
Master单元拥有几个数据结构,用于存储每个map和reduce任务的状态 (idle, in-progress,or completed)和工作机器的标识(for non-idle tasks)
Matser单元是将本地中间文件分块从map task传递到reduce task的通道。因此对于每一个完成了的map task,Master单元将储存 R intermediate file regions 的位置和 sizes。当Map任务完成时,会收到对该位置和大小信息的更新。这些信息会被递增地推送到正在执行reduce任务的工作单元。
Fault Tolerance(容错性)
主节点定期向每个工作节点发送ping请求。如果在规定的时间内没有收到某个工作节点的响应,主节点会标记该节点为失败。该工作节点已经完成的任何map任务会被重置回初始的空闲状态,从而可以在其他工作节点上重新调度。同样,任何在失败的工作节点上进行中的map任务或reduce任务也会被重置为空闲状态,并可重新调度
已完成的map任务会在失败后重新执行,因为它们的输出存储在失败机器的本地磁盘上,因此无法访问。而已完成的reduce任务无需重新执行,因为它们的输出存储在全局文件系统中。
当一个map任务首先由工作节点A执行,然后因为A失败,任务被工作节点B重新执行时,所有执行reduce任务的工作节点都会收到重执行的通知。任何尚未从工作节点A读取数据的reduce任务将会从工作节点B读取数据。
MapReduce对大规模的工作节点故障具有很强的恢复能力。例如,在一次MapReduce操作中,网络维护导致运行中的集群中每次有80台机器变得不可访问,持续几分钟。MapReduce的主节点简单地重新执行了这些无法访问的工作节点的任务,继续向前推进,最终完成了MapReduce操作。
Master Failure
使主节点定期写入上述主节点数据结构的检查点是比较简单的。如果主节点任务崩溃,可以从最后的检查点状态重新启动新的副本。然而,考虑到只有一个主节点,其故障的可能性较小,因此我们当前的实现会在主节点失败时中止MapReduce计算。客户端可以检测到这种情况,并在需要时重试MapReduce操作
Semantics in the Presence of Failures
当用户提供的 map 和 reduce 操作是输入值的确定性(deterministic)函数时,我们的分布式实现能够产生与无故障顺序执行整个程序时相同的输出。
- 确定性(deterministic)函数:相同输入能得到相同的输出
- 非确定性(non-deterministic)函数:相同输入可能会得到不同输出(比如拥有随机性的函数)
我们依赖 map 和 reduce 任务输出的原子提交(atomic commit)来保证这一特性。每个正在执行的任务会将其输出写入私有的临时文件:
- 一个 reduce 任务生成一个临时文件。
- 一个 map 任务生成 R 个临时文件(每个 reduce 任务对应一个文件)。
当 map 任务完成时,工作节点会向主节点发送一条消息,并在其中包含 R 个临时文件的名称。如果主节点收到的 map 任务完成消息对应于一个已经完成的任务,则会忽略该消息。否则,它会将 R 个文件名记录到主节点的数据结构中。
当 reduce 任务完成时,其工作节点会原子地将临时输出文件重命名为最终输出文件。如果同一个 reduce 任务在多个机器上执行,则可能会有多个重命名操作针对同一个最终输出文件。我们依赖底层文件系统提供的原子重命名操作(atomic rename)来确保最终的文件系统状态只包含 reduce 任务的其中一次执行所产生的数据。
绝大多数 map 和 reduce 操作都是确定性的,在这种情况下,我们的语义等价于顺序执行,使程序员更容易推理程序的行为。然而,当 map 或 reduce 操作是非确定性(non-deterministic)时,我们提供的是较弱但仍合理的语义。
在存在非确定性操作的情况下,一个特定的 reduce 任务 R1 的输出等价于该任务在某次顺序执行中产生的结果。然而,另一个 reduce 任务 R2 的输出可能对应于不同的顺序执行中产生的结果。
考虑 map 任务 M 和 reduce 任务 R1、R2,定义 e(Ri) 为 Ri 的最终提交执行(每个 reduce 任务最终只有一次成功的提交执行)。较弱的语义产生的原因如下:
- e(R1) 可能读取的是 M 在某次执行中产生的输出,而
- e(R2) 可能读取的是 M 在不同执行中产生的输出。
因此,在 map 任务 M 被重新执行的情况下,不同的 reduce 任务可能读取 M 在不同执行中产生的输出,从而导致 reduce 任务的最终结果可能来自不同的 map 执行版本。
Locality(数据局部性)
在我们的计算环境中,网络带宽是一种相对稀缺的资源。为了节省网络带宽,我们充分利用输入数据存储在集群机器的本地磁盘这一特性(这些数据由 GFS [Google File System] 管理)。
GFS 将每个文件划分为 64MB 的块,并在不同的机器上存储多个副本(通常是 3 份副本)。MapReduce 主节点会根据输入文件的位置信息,优先将 map 任务调度到包含该任务所需数据副本的机器上。
如果无法将任务调度到存储有该数据副本的机器上,主节点会尽量选择靠近数据副本的机器(例如,调度到同一网络交换机下的其他工作节点)。
当在一个大规模的集群上运行 大规模 MapReduce 任务时,大部分输入数据都可以在本地读取,不会占用网络带宽,从而提高任务执行效率。
Task Granularity(任务粒度)
在 MapReduce 计算框架中,我们将 map 阶段 划分为 M 个任务,将 reduce 阶段 划分为 R 个任务。
- M 和 R 的选择
-
理想情况下,M 和 R 的数量应远大于工作节点的数量(worker machines)。
-
这样做的好处:
1.提高动态负载均衡能力——任务可以更均匀地分配给不同的 worker 机器,避免部分机器过载或空闲。
2.加速故障恢复——如果某个 worker 失效,它已完成的多个 map 任务可以分散到其他 worker 机器上执行,减少整体恢复时间。
- M 和 R 过大的限制
调度开销:主节点(master)需要进行 O(M + R) 次任务调度决策。
内存占用:主节点存储 O(M × R) 的任务状态信息(不过,实际内存占用较小,每个 map-reduce 任务对大约存储 1 字节 的状态信息)。
R 的用户限制:
每个 reduce 任务最终会生成一个单独的输出文件,所以 R 不能太大,否则会产生过多的小文件,影响后续处理。 - 任务粒度的优化
通常,我们选择 M 使得每个任务处理的输入数据大小在 16MB 到 64MB 之间。
这样可以保证数据局部性(Locality Optimization)最大化,减少远程数据传输。
R 一般设置为 worker 机器数量的一个小倍数,以保证 reduce 任务能够较好地并行执行。
例如,在 2,000 台 worker 机器 的计算环境中,我们常用的参数是:
M = 200,000
R = 5,000
这种配置能充分利用集群计算资源,同时保持合理的调度开销,提高任务执行效率。

浙公网安备 33010602011771号