因为工作的原因,接触大数据有些时日了,虽然说不上很熟练了,但是,感觉已经稍微有些入门了。总想写些什么内容,但是一敲键盘又不知道自己想分享些什么?于是乎,今天下了决心从最基础的开始写起,也就是 Hadoop 中最基础的 MapReduce,这应该可是说是 Hadoop 的基石之一,在网络上关于 MapReduce 的资源也很丰富,但是,我在这篇文章中分享的应该和大部分文章都不一样,这篇是理论与实践穿插(并不是结合), 希望能让读的同学有所收获。
从 Python 的 map/reduce 说起
有写过 Python 的同学可能多多少少都听过 map
和 reduce
函数,至于清不清楚这两个函数的作用是什么就难说了,我个人的理解也比较肤浅,所以就给个肤浅的例子:
其中 4-5行 是 map
函数的操作,7-8行 是 reduce
函数的操作,然后结合 10-12 行的输出,我们可以知道 map
和 reduce
函数的作用为:
- map:将数组中的每个元素都应用一次指定的函数(
lambda x:2 * x
) - reduce:将数组中的元素迭代应用指定的函数(
lambda x, y: x + y
)
可能 reduce
解释得不是太清楚,其实 reduce
可以看作是累积的效果,具体诠释可以为:累加/累乘,这样解释还是不好理解,讲两个例子就好理解了:
其实就是拿第 1 个和第 2 个元素调用函数,然后将返回的值作为参数1,拿第 3 个元素作为参数2调用函数,直到所有元素都用完了,那么最后的结果也就是 reduce 的结果了。
从这里也可以看到:map 是输入有几个,输出就有几个,reduce 是输入只要大于0个,那么输出就只有1个
Hadoop 的 MapReduce 概念
将 Python 的 map/reduce 函数实现迁移到 Hadoop 中来是一样的,只不过相对于 Hadoop 的 MapReduce 来说输入和输出没有那么明显,这是因为 Hadoop 的 MapReduce 不是 Python 中函数那么简单,而是一个框架。作为一个框架,那么必然就会替我们做很多事情,所以我们不知道的话就没法玩这个框架。
为了可以玩这个框架,我这里先抽象一下 MapReduce 框架:
这样就简单了,我们只需要针对 Hadoop 编写 Map 和 Reduce 函数就可以了。那么,现在有一个问题:怎么把一个文本文件变成数组?
这其实就是 MapReduce 框架帮我们做的事情啦,通常我们都是将一个文本文件的内容读进来,然后按行分割,每行就是数组中的一个元素,这样,我们的 Map 的输入就是一个数组,数组中的每个元素都是文本文件中的一行。好,这就简单理解了从 文本文件输入到 Map 的这一步。
接着,另外一个问题又来了,怎么讲 Reduce 的结果保存到文本文件中呢?同样的,MapReduce 框架也帮我们实现了这些东西,我们只需要指定我们要保存的位置,然后指定一下保存为文本文件,即可完成,这样也就解决了从 Reduce 结果到文本文件 这一步。
接下来的问题就是 Hadoop 的 Map 和 Reduce 该如何实现了。
实现 MapReduce
实现 Map
前面说的都是简化的东西,到实际中呢稍微有些不一样,为了实际操作,我就以 Hadoop 经典的 WordCount 来示例,分享一下如何实现 MapReduce。在实际编码中,相比我们前面说的会稍微复杂一下,但是没关系,为了便于理解,我会忽略很多参数,将函数代码简化。现在,我们思考一个问题,刚才我们用 Python 写 Map 的时候,传了两个参数:函数 和 数组,对于这里,因为 数组 已经像刚才说的 MapReduce 框架已经帮我们准备好了,每个元素都是文本文件里面的一行,所以我们就准备 函数 就好了,那么代码可以这么写:
这里代码稍微有点多,但是,我们可以简化得理解为:
这里 Text value
我们知道是文本文件中的一行,然后在 map
函数里面分割一下,并返回一个数组,这里和我们之前 Python 的代码效果有点不一致,区别在于:
- Python 的 map 是调用函数的数组有几个元素,返回的数组值就有几个元素
- MapReduce 的 Map 是调用函数的返回值和传入的数组元素不需要一样
就是这样,我们的 Map 函数就写好了,需要注意的是,刚才忽略掉的那些代码都要补上,因为这些是 MapReduce 框架依赖的东西,不能丢弃。接下来写 Reduce 的函数。
实现 Reduce
Reduce 的函数有一点需要注意,那就是一般情况下 Map 的输出不是 Reduce 的输入,那么 Reduce 的输入是啥呢?我们可以看到刚才 Map 的输出是一个个 HashMap
数据结构,key 是单词,value 都是数字1,那如果文本文件里面,有多个相同的单词 Hello,那么就会存在多个 <Hello, 1>
这样的元素,然后 MapReduce 作为框架,肯定是不爽的,看不下去,所以他就会合并掉这些相同的元素,变成: <Hello, [1, 1, 1, ..., 1]
的一个元素,然后我们 Reduce
函数的输入也就有了,还是和 Python 的 reduce
有差别
所以,我们的 Reduce 函数代码就要这么写了:
完成 MapReduce
好的,现在 Map 和 Reduce 都写好了,是时候组装他们了,MapReduce 框架还是有很多规矩的,这些我们照着抄就好了,完整代码已经放到 Github 上了,有需要的同学可以移步这里查看。至于后续打 jar 包啥的在官方文档中已经非常简单了,我这里想讲些不一样的,所以就忽略了。
执行 MapReduce
正如之前所说,在各种介绍的文档中,打出 Jar 包执行 Jar 包都是司空见惯了,但是,基本上大多数文档的相似点都是:运行都在 Hadoop 环境下,也就是说你需要安装 Hadoop 环境。有很多人是本地没有安装 Hadoop 环境的,所以还得跑去有 Hadoop 的环境验证一番,很是麻烦。这里给大家演示一种方法和提示另外一种方法。
不安装 Hadoop 执行 MapReduce 程序
我们回想一下,写 MapReduce 程序的时候好像写了 main
函数,但是执行的时候却是这么执行的:
$ bin/hadoop jar wc.jar WordCount /user/hdfs/wordcount/input /user/hdfs/wordcount/output
那假如我们不这么执行,直接在 IDE 中执行 main
函数会怎么样?事实就是它会以本地模式(local mode)执行!但是,需要注意的是,你不能天然得使用 HDFS,此外还有两个比较大的缺点分别是:
- 这种方式是单线程运行的,而且只能运行一个 Reducer,及时调用了
setNumReducers
来设置 Reducer 的数量也是无济于事的。 - 虽然这种方式是运行在内存中,但是,如果你执行了本地文件系统的内容,也会产生文件I/O,这势必会让代码运行时间加长。
虽然有一些缺点,但是这种方式对于我们调试 MapReduce 函数来说简直是神器,方便得不行。
这是方式一,除此之外,Hadoop 还提供了一个叫做 MiniMRCluster 的测试套件,你可以使用它来编写单元测试,但是,也不仅仅是单元测试,它可以当作是 MapReduce 的本地框架,我们也可以基于它进行开发 MapReduce 应用。
小结
关于 MapReduce 的基础知识我觉得就差不多这么多了,既然说是基础,那么肯定关于 MapReduce 的内容不止这么多了,后续我至少还会写 进阶 和 高级 两篇,同时也会根据需要做一些更深入的分享。