背景
MapReduce 的提出和 Google 其本身的业务特点有很强的关联。Google 以搜索引擎和广告业务为核心,有很多诸如爬虫处理、网页请求日志、倒排索引等特定的大量数据的大规模计算业务,尽管这些任务的实现逻辑大多都是非常简单直接的,但由于数据规模的庞大,往往需要在成百上千台不同的机器上进行并行数据存取和计算,才能在合理的时间内得到结果。
为了简化分布式编程的难度,Google 基于以 Lisp 为代表的函数式编程语言和矢量编程语言思想,提出了 MapReduce 框架,将原本复杂的分布式编程任务通过 MapReduce 框架进行抽象简化,隐去了并行化、故障容错、数据分配、负载均衡等步骤,使得程序员仅需要根据具体任务设计 Map 和 Reduce 这两个函数,从而,使得分布式任务的开发不再依赖于分布式系统的专家,极大地简化了分布式任务开发的难度。
模型框架及经典任务
Map 函数:在输入的 <K, V> 对上应用此函数,得到一个中间过程的 <K, V> 对集合,并且把相同 key 值对应的不同 value 打包,传递给 Reduce 函数。相当于可以把这个 <K, V> 的 map 看成是一个 multiset,每次给 Reduce 函数传递一个 key 对应的 set 。
Reduce 函数:接收 Map 计算后的中间过程结果,合并中间数据,得到最终的结果。
任务 | 具体描述 | Map | Reduce |
单词计数 | 统计一系列大文档中的每个单词的出现次数。 | 输出单个文档切片中每个单词的出现次数。 | 把 Map 产生的每一个特定的单词的出现数累加。 |
分布式 Grep | Grep ( Global Regular Expression Print ) 使用正则表达式搜索文本,并把匹配的行打印出来。 | 输出匹配某个正则表达式的一行。 | 把 Map 产生的中间数据复制到输出,相当于一个恒等函数。 |
统计 URL 访问频率 | 类似于单词计数,统计一系列处理日志中某个网页的访问请求频率。 | 处理网络日志输入,对每个访问输出 <URL, 1>。 | 把 Map 产生的每个相同 URL 的值都统计在一起,并输出 <URL, total count>。 |
翻转网页链接图 | 给定一个有向图,其中的每条边 <u, v> 表示网站 u 可以链接到网页 v。对每一个网页 v,输出可以链接到该 v 的所有网站 u。 | 对于每个输入的 <source, target>,翻转输出 <target, source>。 | 把 Map 产生的所有的 target 相同的 source 列表合并,输出 <target, list(source)>。 |
主机术语向量 | 术语向量是一个或多个文档中最重要的词语组成的 <word, frequency> 对。 | 对于每个输入文档,提取 hostname, 并生成 <hostname, term vector>。 | 把 Map 产生的指定主机的所有文档的术语向量加在一起,并丢弃低频词,生成最终的 <hostname, term vector>。 |
倒排索引 | 搜索引擎需要将形如 key→value 的正向索引重构为 value→key 的倒排索引。 | 处理每个文档,生成 <word, document ID>。 | 接收所有的 key-value 对,并且给定的 word,对其所有的 document ID 列表进行排序,输出 <document, list(document ID>。 |
分布式排序 | 即根据给定的规则进行大数据排序。 | 从每个记录中提取 key 值,并生成 <key, record>。 | 接受所有的 key-value 对,不修改其内容,把相同 key 的 pairs 给同一个 reduce 进行处理。 |
Hadoop 对于原始输入到 Map 的处理默认按照 WordCount 任务进行,即给定大文本,Hadoop会将文本切片,然后按照 <片内偏移,文本> 的形式送到 Map 函数。如需修改可以重设参数,或者重写对输入的处理。
实现
这里只讨论论文中提到的实现。比较重要的背景有:
- 网络带宽较低,需要尽量减少网络传输 ===> 考虑让处理任务的工作机和存储原始数据的工作机尽量是同一个,或者距离比较近
- 集群庞大,所以机器故障很常见 ===> 需要容错处理
- 存储设施便宜,因此管理存储数据的内部分布式文件系统通过复制拷贝的方法来提高系统的可靠性 ===> 也是之后系统中 master 机故障的处理方法之一
- 用户将工作提交到调度系统,每个工作都有一系列任务,由调度系统安排可用的机器来执行 ===> 整个框架的基础
下面来看一下 Google 对于整个系统流程的设计:
Figure 1. Execution Overview
整个系统流程也是相对比较简单直接的。用户通过接口将工作提交到集群,集群中的 Master 机会将该工作的输入分配到 M 个空闲的工作机执行对应的 Map 任务。执行 Map 任务的工作机(后简称 Map 机)会从内置的分布式文件系统中读取对应的输入切片,解析出 key-value 对后送到用户自定义的 Map 函数中进行进一步的计算处理,将产生的中间结果会被划分成 R 块缓存在该工作机本地,并通知 Master 机这些数据在 Map 机上的对应位置、大小等信息,Master 机再挑选 R 个空闲的工作机去执行后续的 Reduce 任务。每个执行 Reduce 任务的工作机 (后简称 Reduce 机)会远程读取这些中间结果,并且在读取完所有分配给自己处理的中间结果之后,Reduce 机会将这些数据按照其 key 值排序打包(通常需要外排序)。这个处理完的结果就可以直接被用户定义的 Reduce 函数使用,计算出最终的结果。虽然这个时候所有的结果仍然分布在集群的各个工作机之中,但由于大多数分布式计算出的结果往往会成为下一个分布式计算的输入,所以把文件合并的操作往往是不必要的。因此,我们只需要 Master 机去唤醒一下之前沉睡的用户进程,并将这些文件(或文件地址)返回给用户即可。
容错处理
- 工作机: Master 机每隔一段时间就会 ping 一下每个工作机。如果工作机在一段时间内都没有响应,就会认为工作机故障了。由于只有 Reduce 机的最终计算结果会存储在全局的文件系统中,其他计算任务和中间过程结果都是存在本地的,因此,只有完成了 Reduce 任务的工作机发生的故障可以忽略,其他故障一旦发生,该工作机被分配的任务必须被重新执行,并将该工作机重置为空闲状态。需要注意的是,如果一个 Map 任务先被分配到了工作机 A,后来由于发生故障,由工作机 B 重新执行了 Map 任务的话,所有尚未从工作机 A 读取到数据的 Reduce 机都必须从工作机 B 中读取数据(已经完成读取的可以忽略)。也因此,MapReduce 可以适应大规模的工作机故障,因为 Master 机会不断地重新执行那些暂时不能访问的工作机上正在执行的任务来推进整个工作的进度,直到整个工作被完成。
- Master 机:由于 Master 机是单机,因此论文作这认为 Master 机宕机的概率比较小,给出的一个处理方法是增加多个检查点,当 master 挂了的时候,从上一个检查点恢复。所以一旦 Master 机彻底挂了的话,整个 MapReduce 计算也会终止。如果想避免这个情况,或许可以考虑由其他 client 机来检查这个 Master 机的状态,并且在 Master 机挂了的时候尝试恢复或重新执行 MapReduce。