一:目的
之前面试曾遇到面试官让用python代码实现mapreduce中最简单的demo WordCount,由于之前一直用java来写hadoop程序,突然转到python,是我产生了质疑,python与hadoop应该是不兼容的,即使写出来程序,到时候怎么运行?一头雾水最后导致面试失败。后来通过查阅资料,研究mapreduce的底层实现,发现尽管Hadoop框架是用Java编写的,但是为Hadoop编写的程序不必非要Java写,还可以使用其他语言开发,比如Python或C++(Haoop在0.14.1版本提供C++ API),而mapreduce只是一种思想,跟语言无关。$HADOOP_HOME/src/examples/python/WordCount.py,你就可以明白我的意思了。
现在将会采用python语言实现wordcount并在hadoop上运行实现。
二:Python代码
map实现:
下面Python代码的一个“窍门”是我们将使用Hadoop流API(可以看下相关的维基条目)来帮助我们通过STDIN(标准输入)和STDOUT(标准输出)在Map和Reduce代码间传递数据。我们只是使用Python的sys.stdin读取输入数据和打印输出到sys.stdout。这就是我们需要做的,因为Hadoop流将处理好一切。将下面的代码保存在文件 /home/hduser/mapper.py 中。它将从STDIN读取数据,拆分为单词并输出一组映射单词和它们数量(中间值)的行到STDOUT。尽管这个Map脚本不会计算出单词出现次数的总和(中间值)。相反,它会立即输出( 1)元组的形式——即使某个特定的单词可能会在输入中出现多次。在我们的例子中,我们让后续的Reduce做最终的总和计数。当然,你可以按照你的想法在你自己的脚本中修改这段代码,但是,由于教学原因,我们在本教程中就先这样做。:-)
请确保该文件具有可执行权限(chmod +x /home/hduser/mapper.py ),否则你会遇到问题。
reduce实现:
将下面的代码保存在文件 /home/hduser/reducer.py 中。它将从STDIN读取mapper.py的结果(因此mapper.py的输出格式和reducer.py预期的输入格式必须匹配),然后统计每个单词出现的次数,最后将结果输出到STDOUT中。
请确保该文件具有可执行权限(chmod +x /home/hduser/reducer.py ),否则你会遇到问题。
代码测试(cat data | map | sort | reduce):
在MapReduce作业中使用它们之前,我建议先在本地测试你的mapper.py和reducer.py脚本。否则,你的作业可能成功完成了但没有作业结果数据或得到了不是你想要的结果。如果发生这种情况,很有可能是你(或我)搞砸了。这里有一些想法,关于如何测试这个Map和Reduce脚本的功能。
运行代码:
下载示例输入数据
下载每个文件为纯文本文件,以UTF-8编译并且将这些文件存储在一个临时目录中,如/tmp/gutenberg。
说明:你将需要在你的Cloudera虚拟机中打开浏览器。选择适当的文件下载(UTF-8 版本),它将显示在你的浏览器中。点击鼠标右键按钮来保存该文件。给它一个合适的名称(如”Ulysses”),并注意它将保存在下载目录中。
将本地示例数据拷贝到HDFS
在我们运行实际的MapReduce作业前,我们首先必须从我们本地文件系统中拷贝文件到Hadoop的HDFS内。
*说明:
我们假设你是在你的下载目录中。我们必须在HDFS中创建一个子目录,然后拷贝文件过来。最后,我们验证拷贝文件成功。
首先,我们在HDFS中创建子目录MyFirst:
[cloudera@quickstart Downloads]$ hadoop fs -mkdir MyFirst
然后,我们拷贝文件。注意,三个文件以.txt结尾:
[cloudera@quickstart Downloads]$ hadoop fs -copyFromLocal *.txt MyFirst
最后,我们验证拷贝成功:
[cloudera@quickstart Downloads]$ hadoop fs -ls MyFirst
Found 3 items
-rw-r–r– 1 cloudera cloudera 1423803 2014-11-30 08:02 MyFirst/Leonardo.txt
-rw-r–r– 1 cloudera cloudera 674570 2014-11-30 08:02 MyFirst/OutlineOfScience.txt
-rw-r–r– 1 cloudera cloudera 1573150 2014-11-30 08:02 MyFirst/Ulysses.txt
运行MapReduce作业
*说明:
运行MapReduce作业,敲入如下命令:
[cloudera@quickstart ~]$ hadoop jar /usr/lib/hadoop-0.20-mapreduce/contrib/streaming/hadoop-streaming.jar -file mapper.py -mapper mapper.py
-file reducer.py -reducer reducer.py -input MyFirst/* -output MyFirst4-output
你会收到有关文件被弃用的警告,不用担心。重要的是:当你发出这条命令时,输出目录(在这个示例中是MyFirst-output)不存在。
验证这个程序工作正常。首先,输入命令:hadoop fs -ls MyFirst4-output
[cloudera@quickstart ~]$ hadoop fs -ls MyFirst4-output
Found 2 items
-rw-r–r– 1 cloudera cloudera 0 2014-11-30 09:23 MyFirst4-output/_SUCCESS
-rw-r–r– 1 cloudera cloudera 880829 2014-11-30 09:23 MyFirst4-output/part-00000
然后,查看输出文件:
[cloudera@quickstart ~]$ hadoop fs -cat MyFirst4-output/part-00000
将文件从HDFS中拷入到你本地文件系统中:
[cloudera@quickstart ~]$ hadoop fs -copyToLocal MyFirst4-output/part-00000
MyFirstOutputLocal.txt
现在,一切都准备好了,我们终于可以在Hadoop集群上运行我们的Python MapReduce作业了。如上所述,我们使用Hadoop流API通过STDIN和STDOUT在Map和Reduce间传递数据。
如果你想要在运行的时候修改Hadoop参数,如增加Reduce任务的数量,你可以使用-D选项:
hduser@ubuntu:/usr/local/hadoop$ bin/hadoop jar contrib/streaming/hadoop-streaming.jar -D mapred.reduce.tasks=16 …
关于mapred.map.tasks说明:Hadoop does not honor mapred.map.tasks beyond considering it a hint。但是,Hadoop接受用户指定mapred.reduce.tasks并且不操作。你不能强制指定mapred.map.tasks,但可以指定mapred.reduce.tasks。
这个任务将读取HDFS目录/user/hduser/gutenberg中的所有文件,处理它们,并将结果存储在HDFS目录/user/hduser/gutenberg-output中。一般情况下,Hadoop对每个reducer产生一个输出文件;在我们的示例中,然而它将只创建单个文件因为输入的文件都很小。
在终端中前一个命令的输出示例︰
使用Python语言写Hadoop MapReduce程序
**译者说明:截图中的命令不完整,完整命令如下:
hduser@ubuntu:/usr/local/hadoop$ bin/hadoop jar contrib/streaming/hadoop-streaming.jar -mapper /home/hduser/mapper.py -reducer /home/hduser/reducer.py -input /user/hduser/gutenberg/* -output /user/hduser/gutenberg-output