MapReduce: simplified data processing on large clusters

DOI:10.1145/1327452.1327492

相关笔记:

总结

这篇论文面对的场景是对 TB/PB 级的全量数据进行批量计算,且数据之间相互独立,例如网页索引、日志统计。在这之前,大规模并行计算的主要方案是 MPI。MPI 面对的是科学计算场景——节点之间存在复杂的数据依赖和频繁的通信需求,这类计算任务无法被简单的高层抽象覆盖,所以 MPI 选择暴露底层原语,把控制权交给程序员。

这篇论文发现 Google 内部几乎所有的大规模数据处理任务,计算逻辑都可以被表达为两类操作:对每条数据独立处理(Map),和把相同 key 的数据合并(Reduce)。这是一个关键洞察,识别出一类问题的结构性特征,然后设计一个受限但自动化的框架来覆盖这类问题。

基于这个洞察,她们把所有计算逻辑限制为 Map 和 Reduce 两个无状态的函数,框架完全接管并行化、容错、调度的所有细节,让没有分布式系统经验的普通工程师也能在数千台机器上处理大规模数据。

她们选择了极度简化的编程模型,因此划定了 MapReduce 的边界:不适用于实时流处理、不适用于低延迟场景、不适用于数据之间存在复杂依赖的场景。

因为 MapReduce 是批处理方案,两个阶段是全量的,所以它不适用于实时流处理,也不适用于低延迟场景,因为编程模型只能表达 map 和 reduce 两个操作,数据之间存在复杂依赖的场景,无法被map和reduce两个操作进行表达。

阅读

前提与假设

场景假设(应用场景):

环境假设(运行在什么基础设施和故障模型下):

逻辑与数学假设:

方法设计

Pasted image 20260801033819.png

论文将一次 MapReduce 执行划分为 M 个 Map 分片和 R 个 Reduce 分区。以下是基于论文 图 1 和 第 3.1 节 的标准化执行流程:

这些流程直接来源于论文,更偏向设计的角度。在 MapReduce 设计流程 中,我会通过举例,以实现的角度出发,重新阐述各个阶段。

阶段 0:初始化与拆分(启动)

阶段 1:Map 任务执行(数据读取与映射)

阶段 2:分区与溢写(Partitioning & Local Write)

阶段 3:混洗与排序(Shuffle & Sort)

阶段 4:Reduce 任务执行(归约与输出)

阶段 5:完成与返回

详见 MapReduce 设计流程

实验设计

作者是如何设计实验验证其方案的?使用了哪些 Benchmark、Baseline 和指标?

Benchmark:基准任务

Benchmark 是指实验对象;Baseline 是指比较对象。
由于论文篇幅有限,不可能枚举所有场景,所以作者选择了两个极端且对立的任务,证明系统在两个极端的资源瓶颈下都能跑的好:

  • Grep(扫描类):I/O 瓶颈。几乎不占 CPU,不占网络,只测硬盘能读多快。用来证明“数据本地化”起作用了,能把硬盘读满
  • Sort(排序类):网络 +CPU 瓶颈。所有数据都要重新打乱分发(Shuffle)。用来证明“网络调度”和“Combiner”起作用了

实现

MIT6.5840 Lab 1:MapReduce

评价

一、这篇论文在解决什么问题?

问题是从哪来的?

这篇论文解决了什么问题?

为什么之前没人解决?

1.问题域的特殊性。

2003 年以前,分布式系统的出现并非为了处理 " 大数据 ",而是为了解决单机无法独立承担的若干根本性问题:

Google 的场景是全新的。它的数据量以 TB/PB 计、需要对全量数据做批量计算、且任务本身并不复杂。

2.底层基础设施的历史积累

MapReduce 的高性能依赖于数据本地化调度:将计算任务派发到持有对应数据的节点,以避免海量的网络传输开销。而这一调度策略的前提,是 GFS(Google 的分布式文件系统)已经提供了块级别的数据位置信息。

值得注意的是,GFS 与 MapReduce 面对的是同一个问题域,两者几乎同期诞生于 Google 内部。换言之,在 MapReduce 出现之前,Google 已经为它量身建好了地基。真正解决这个问题域的,是 GFS 与 MapReduce 的组合,而非 MapReduce 单独完成的。

3.对问题的精准洞察,以及敢于做激进的 trade-off

Google 的核心洞察在于:她们面对的绝大多数大规模任务(网页索引、日志统计、倒排索引)在结构上高度同构。对一堆相互独立(互不依赖)的数据,每条数据做相同的变换处理,最后再把结果聚合。

我们对这两步进行命名,就产生了 map 和 reduce 两个操作:谷歌的大规模任务都可以以这种方式来表达:Map,然后 Reduce。

这一步实际上进行了编程模型的设计选择。当选择了只能用 map 跟 reduce 两种方式来表达时,就意味着已经放弃了编程模型的通用性,数据之间有依赖的任务,无法用这个框架来表达。

在顺序执行的情况下,一个任务是这样的:它对每条数据依次执行 map 任务,map 任务结束之后,再对所有中间数据依次执行 reduce。

但是,由于每条数据都是独立的、互不依赖的,所以它们可以并行处理。因此,我们可以将顺序执行的单机扩展为并行执行的分布式集群。集群中的每台电脑可以同时执行 map 任务,所有 map 任务结束之后,再对中间数据同时执行 reduce。

当我们从单机扩展为分布式集群的时候,业务逻辑(也就是 map 跟 reduce 中的逻辑)并没有任何改变。唯一改变的是,我们需要在这基础上增加分布式系统底层细节,比如容错、调度等等。

而对于不同的任务,它们仅仅是业务逻辑不同,分布式系统的底层细节却是完全相同的。因此,我们可以将这两部分进行分离,由框架负责底层细节,程序员负责 map 跟 reduce 中业务逻辑的书写。

这一步实际上进行了接口抽象层次的设计选择。分离之后,程序员虽然不需要负责底层细节,但这也意味着程序员无法再去干预这些底层细节了。

Note

在这一小节里,引用块是我推理之后再额外加上的。我按照问题域的思路进行推理时,并没有思考过我在进行什么样的取舍。这是否意味着,当我在设计某一个系统的时候,并不需要以取舍的角度去看,而是以问题的角度去看呢?

  • 是的。设计时以问题为起点,而非以取舍为起点。取舍是对设计的事后描述,问题域的结构特征才是设计的真正驱动力。当一个设计感觉理所当然的时候,往往意味着设计者真正理解了问题。反过来看,如果在设计过程中感到取舍是一件非常困难的事情,这意味着自己可能并没有清楚自己所面对的问题域是什么。

那我为什么要知道这个系统的取舍呢?考虑不同维度的取舍还有什么意义呢?

  • 作为系统的使用者,只有清楚了系统的取舍,才能知道系统的边界和局限性。
  • 作为系统的设计者。我们在理解 MapReduce 的时候,并不是为了去用它,而是为了站在它的肩膀上去思考下一步应该怎么做。只有知道 MapReduce 的取舍,我们才能找到下一步有价值的问题。
    • 例如,在编程模型维度的取舍上,MapReduce 牺牲了通用性,那么我们能否修改编程模型,使得它支持更多的操作?(Spark 的出发点)

所以这篇论文的产生,关键之处在于:

  1. 问题产生的土壤,以及对问题的洞察
  2. 其她技术的突破,使得 MR 能够在此基础上创新
  3. 勇敢的 trade-off。

二、这篇论文的核心洞察(Key Insight)是什么?

核心洞察:Google 内部几乎所有的大规模数据处理任务,计算逻辑都可以被表达为两类操作:对每条数据独立处理(Map),和把相同 key 的数据合并(Reduce)。抽象地说:把复杂的并行问题归纳为少数几种操作模式,让框架自动处理并行和容错


这个洞察的迁移价值在于它背后更抽象的原则:识别出一类问题的结构性特征,然后设计一个受限但自动化的编程模型来覆盖这类问题。

这些系统的共同点是:都找到了某类问题的结构性特征,用受限的编程模型覆盖这类问题,从而让框架可以自动处理底层复杂性。受限换自动化,这是 MapReduce 洞察最本质的迁移价值。

三、基于这个洞察,论文做出了哪些设计选择?

核心选择

连锁选择

基础设施选择

四、这篇论文的局限与边界?

即第三问设计选择所决定的代价和边界。

局限性

来自编程模型的限制

来自执行拓扑的限制

来自中心化 Master 的限制

来自任务重试的限制


边界

实时流处理场景

低延迟场景

任务不可重试的场景

数据之间存在复杂依赖的场景

需要细粒度资源控制的场景

五、这篇论文和其她工作的关系是什么?

-:同时代有没有解决类似问题的不同方案?

往前看

前人方案是什么,为什么不够?

前人处理大规模并行计算的主要方案是 MPI(Message Passing Interface)。MPI 给程序员完全的控制权——任意节点间通信、任意同步模式、任意数据传输。这在科学计算场景(气候模拟、基因组测序)下是合理的,因为那些计算任务本身就有复杂的数据依赖,无法被简单的高层抽象覆盖。

但 MPI 在 Google 的场景下有两个根本缺陷:

第一,容错完全由程序员负责。MPI 没有内置的容错机制,节点故障时程序员需要自己处理状态恢复和任务重启。在数千台廉价机器组成的集群上,故障是常态而非例外,把容错压在程序员身上是不现实的。

第二,学习和使用门槛极高。Google 的工程师大多不是分布式系统专家,让他们手动管理节点间通信和同步是巨大的工程负担。

这篇论文的出现是必然的吗?

在 Google 的特定场景下是必然的——TB 级批量数据处理的需求已经存在,GFS 提供了存储基础,缺的只是计算层的抽象。但 MapReduce 这个具体形式不是唯一可能,有人可以设计出不同的编程模型来解决同样的问题。MapReduce 的出现是必然的,但它的具体形态是 Google 对问题洞察的产物。


往后看

直接改进 MapReduce 局限性的工作

Spark(2010):针对 MapReduce 无法高效表达迭代计算的局限,把编程模型从 Map+Reduce 扩展到任意 DAG 变换,并把中间结果保留在内存而非写入磁盘。同样的核心洞察(固定操作模式 + 框架自动化),但覆盖了更广的计算场景。

Apache Tez(2013):针对 MapReduce 固定两阶段执行拓扑的局限,允许用户定义任意 DAG 的执行计划,消除了不必要的 Shuffle 屏障。

把 MapReduce 思想用在新场景的工作

Pregel(2010):把 MapReduce 的洞察迁移到图计算场景——把图算法归纳为以顶点为中心的消息传递模式(BSP),让框架自动处理图的分布式执行。

Hadoop(2006):MapReduce 的开源实现,把 Google 内部系统带给了整个工业界,直接催生了大数据生态系统。

Hive、Pig(2008-2009):在 MapReduce 之上构建更高层的查询语言(类 SQL),进一步降低了大数据处理的使用门槛。

流处理方向

Storm(2011)、Spark Streaming(2012)、Flink(2014):针对 MapReduce 无法处理实时数据流的边界,把批处理模型扩展到流处理场景。Flink 最终实现了流批一体——同一套编程模型同时支持有界批处理和无界流处理。


横向比较

Dryad(2007,Microsoft Research)

与 MapReduce 同时代,解决的是同样的问题——大规模数据并行处理。但 Dryad 选择了不同的方向:允许用户定义任意 DAG 的执行拓扑,而不是固定的 Map+Reduce 两阶段。

这说明当时存在两种不同的洞察:

历史证明 MapReduce 的方向在工业界更成功——简单性和低门槛的价值超过了表达能力的限制。但 Dryad 的思想最终通过 Spark 和 Tez 回归了主流。

Hadoop vs. MapReduce 原版

Hadoop 是 MapReduce 的开源实现,但并非简单复制。Hadoop 在资源管理上做出了改进——YARN(2012)把资源管理从 MapReduce 框架中剥离出来,使得同一个集群可以同时运行 MapReduce、Spark 等不同的计算框架,解决了原始 MapReduce 资源利用率低的问题。