在大数据时代,除了需要解决大规模数据的高效存储问题,还需要解决的另一个问题,就是如何对大规模数据进行高效处理?我们知道Hadoop的有两个核心技术,一个是HDFS(解决了大规模数据的存储问题),另一个是MapReduce。而MapReduce这个核心技术就是解决如何对大规模数据进行高效处理的并行编程模型。
一、分布式并行编程
在过去的很长一段时间里,CPU的性能都会遵循”摩尔定律“,在性能上每隔18个月左右就是提高一倍。那个时候,不需要对程序做任何改变,仅仅通过使用更优秀的CPU,就可以进行性能提升。但是现在,在CPU性能提升的道路上,人类已经到达了制作工艺的瓶颈,因此,我们不能再把希望寄托在性能更高的CPU身上了。
现在这个时候,大规模数据存储在分布式文件系统上,人们也开始采用分布式并行编程来提高程序的性能。分布式程序运行在大规模计算机集群上,集群是大量的廉价服务器,可以并行执行大规模数据处理任务,这样就获得了海量的计算能力
分布式并行编程比传统的程序有明显的区别,它运行在大量计算机构成的集群上,可以充分利用集群的并行处理能力;同时,通过向集群中增加新的计算节点,就可以很容易的实现集群计算能力的扩展。
Hadoop MapReduce是谷歌提出的分布式并行编程模型MapReduce论文的开源实现,运行在分布式文件系统HDFS上的并行编程模型。MapReduce的优势在于处理大规模数据集(1TB以上)。
二、MapReduce模型简介
我们应该知道,Hadoop的MapReduce核心技术起源于谷歌在2004年发表的关于MapReduce系统的论文介绍。论文中有这么一句话:Our abstraction is inspired by the map and reduce primitives present in Lisp and many other functional languages。这句话提到了MapReduce思想来源,大致意思是,MapReduce的灵感来源于函数式语言(比如Lisp)中的内置函数map(映射)和reduce(规约)。
简单来说,在函数式语言里,map表示对一个列表(List)中的每个元素做计算,reduce表示对一个列表中的每个元素做迭代计算。它们具体的计算是通过传入的函数来实现的,map和reduce提供的是计算的框架。我们想一下,reduce既然能做迭代计算,那就表示列表中的元素是相关的(比如我想对列表中的所有元素做相加求和,那么列表中至少都应该是数值吧)。而map是对列表中每个元素做单独处理的,这表示列表中可以是杂乱无章的数据。这样看来,就有点联系了。在MapReduce里,Map处理的是原始数据,自然是杂乱无章的,每条数据之间互相没有关系;到了Reduce阶段,数据是以key后面跟着若干个value来组织的,这些value有相关性,至少它们都在一个key下面,于是就符合函数式语言里map和reduce的基本思想了。
MapReduce任务过程分为两个处理阶段:map阶段和reduce阶段。每阶段都以键值对作为输入和输出,其类型由程序员来选择。程序员需要为每个阶段写一个函数,分别是map函数和reduce函数。
在执行MapReduce任务时,一个大规模的数据集会被划分成许多独立的等长的小数据块,称为输入分片(input split)或简称"分片"。Hadoop为每个输入分片分别构建一个map任务,并由该任务来运行用户自定义的map函数,从而处理分片中的每条记录。map任务处理后的结果会继续作为reduce任务的输入,最终由reduce任务输出最后结果,并写入分布式文件系统。
MapReduce设计的一个理念是“计算向数据靠拢”(移动计算),而不是“数据向计算靠拢”(移动数据)。因为移动数据需要大量的网络传输开销,尤其是在大规模数据环境下,这种开销尤为惊人,所以移动计算要比移动数据更加经济。所以,在一个集群中,只要有可能,MapReduce框架就会将Map程序就近的在HDFS数据所在的节点上运行,即将计算节点和存储节点放在一起运行,从而减少节点间的数据移动开销。正因为这样,MapReducekeyi 并行的进行处理,解决计算效率问题。
三、Map和Reduce函数
MapReduce模型的核心是Map函数和Reduce函数,二者都是由程序员负责具体实现的。MapReduce编程值所以比较容易,是因为程序员只要关注如何实现Map和Reduce函数,不需要处理并行编程中的其他各种复杂问题,如分布式存储,工作调度,负载均衡,容错处理,网络通信等,这些问题都会由MapReduce框架负责处理。
Map函数和Reduce函数都是以<key,value>作为输入,按一定的映射规则转换成另一个或一批<key,value>进行输出(见下表)。
函数 | 输入 | 输出 | 说明 |
map | <k1,v1> | List(<k2,v2>) | 1.将输入分片解析成一批<k1,v1>,输入Map函数中进行处理 2.每一个输入的<k1,v1>会输出一批<k2,v2>。<k2,v2>是计算的中间结果 |
reduce | <k2,List(v2)> | <k3,v3> | 输入的中间结果<k2,List(v2)>中的List(v2)表示是一批属于同一个k2的value |
Map函数的输入是来自于分布式文件系统的文件块,这些文件块的格式是任意的,可以是文档,也可以使二进制格式的。文件块是一系列元素的集合,这些元素也是任意类型的,同一个元素不能跨文件块存储。Map函数将输入的元素转换成<key,value>形式的键值对,键和值得类型也都是任意的,其中,键不同于一般的标志属性,即键没有唯一性,不能作为输出的身份标识,即使是同一输入元素,也可通过一个Map任务生成具有相同键的多个<key,value>。
Reduce函数的任务就是将输入的一些列具有相同键的键值对以某种方式组合起来,输出处理后的键值对,输出结果会合并成一个文件。用户可以指定Reduce任务的个数(如n个),并通知实现系统,然后主控进程通常会选择一个Hash函数,Map任务输出的每个键都会经过Hash函数计算,并根据哈希结果将该键值对输入相应的Reduce任务来处理。对于处理键为k的Reduce任务的输入形式为<k,<v1,v2,...vn>>,输出为<k,V>。
咱举一个简单的案例。比如,想使用MapReduce程序来统计一个文本文件中每个单词出现的次数。对于Map函数的输入<k1,v1>来说,其具体数据就是<某一行文本在文件中的偏移位置,该行文本的内容>。用户可以自己编写Map函数处理过程,把文件中的一行读取后解析出每个单词,生成一批中间结果<单词,出现次数>,然后,把这些中间结果作为Reduce函数的输入,Reduce函数的具体处理过程也是由用户自己编写的,用户可以将相同单词的出现的次数进行累加,得到每个单词出现的总词数。
------------------------------------------如有疑问,敬请留意-----------------------------------------------------------