我的 MapReduce 理解 - 初阶

因为工作的原因,接触大数据有些时日了,虽然说不上很熟练了,但是,感觉已经稍微有些入门了。总想写些什么内容,但是一敲键盘又不知道自己想分享些什么?于是乎,今天下了决心从最基础的开始写起,也就是 Hadoop 中最基础的 MapReduce,这应该可是说是 Hadoop 的基石之一,在网络上关于 MapReduce 的资源也很丰富,但是,我在这篇文章中分享的应该和大部分文章都不一样,这篇是理论与实践穿插(并不是结合), 希望能让读的同学有所收获。

从 Python 的 map/reduce 说起

有写过 Python 的同学可能多多少少都听过 mapreduce 函数,至于清不清楚这两个函数的作用是什么就难说了,我个人的理解也比较肤浅,所以就给个肤浅的例子:

其中 4-5行map 函数的操作,7-8行reduce 函数的操作,然后结合 10-12 行的输出,我们可以知道 mapreduce 函数的作用为:

  • 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 的 MapReduce 该如何实现了。

实现 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

好的,现在 MapReduce 都写好了,是时候组装他们了,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,此外还有两个比较大的缺点分别是:

  1. 这种方式是单线程运行的,而且只能运行一个 Reducer,及时调用了 setNumReducers 来设置 Reducer 的数量也是无济于事的。
  2. 虽然这种方式是运行在内存中,但是,如果你执行了本地文件系统的内容,也会产生文件I/O,这势必会让代码运行时间加长。

虽然有一些缺点,但是这种方式对于我们调试 MapReduce 函数来说简直是神器,方便得不行。

这是方式一,除此之外,Hadoop 还提供了一个叫做 MiniMRCluster 的测试套件,你可以使用它来编写单元测试,但是,也不仅仅是单元测试,它可以当作是 MapReduce 的本地框架,我们也可以基于它进行开发 MapReduce 应用。

小结

关于 MapReduce 的基础知识我觉得就差不多这么多了,既然说是基础,那么肯定关于 MapReduce 的内容不止这么多了,后续我至少还会写 进阶高级 两篇,同时也会根据需要做一些更深入的分享。

Reference

  1. Running Mapreduce in Local Mode
  2. Hadoop Docs
  3. 深入理解 Hadoop

· EOF ·

最近爬虫很是凶猛,标注下文章的原文地址: https://liuliqiang.info/post/thinking-about-map-reduce-primer/