0、 Hadoop Streaming 原理和优缺点
Hadoop 本身是用 Java 开发的,程序也需要用 Java 编写,但是通过 Hadoop Streaming,我们可以使用任意语言来编写程序,让 Hadoop 运行。
Hadoop Streaming 就是通过将其他语言编写的 mapper 和 reducer 通过参数传给一个事先写好的 Java 程序(Hadoop 自带的 *-streaming.jar),这个 Java 程序会负责创建 MR 作业,另开一个进程来运行 mapper,将得到的输入通过 stdin 传给它,再将 mapper 处理后输出到 stdout 的数据交给 Hadoop,经过 partition 和 sort 之后,再另开进程运行 reducer,同样通过 stdin/stdout 得到最终结果。因此,我们只需要在其他语言编写的程序中,通过 stdin 接收数据,再将处理过的数据输出到 stdout,Hadoop Streaming 就能通过这个 Java 的 wrapper 帮我们解决中间繁琐的步骤,运行分布式程序。
原理上只要是能够处理 stdio 的语言都能用来写 mapper 和 reducer,也可以指定 mapper 或 reducer 为 Linux 下的程序(如 awk、grep、cat)或者按照一定格式写好的 java class。因此,mapper 和 reducer 也不必是同一类的程序。
0.1. Hadoop Streaming 的优缺点
优点:
- 可以使用自己喜欢的语言来编写 MapReduce 程序(不必非得使用 Java)
- 不需要像写 Java 的 MR 程序那样 import 一大堆裤,在代码里做很多配置,很多东西都抽象到了 stdio 上,代码量显著减少。
- 因为没有库的依赖,调试方便,并且可以脱离 Hadoop 先在本地用管道模拟调试。
缺点:
- 只能通过命令行参数来控制 MapReduce 框架,不像 Java 的程序那样可以在代码里使用 API,控制力比较弱。
- 因为中间隔着一层处理,效率会比较慢。
- 所以 Hadoop Streaming 比较适合做一些简单的任务,比如用 Python 写只有一两百行的脚本。如果项目比较复杂,或者需要进行比较细致的优化,使用 Streaming 就容易出现一些束手束脚的地方。
0.2、 使用 Python 编写 Hadoop Streaming 程序注意事项:
- 在能使用 iterator 的情况下,尽量使用 iterator,避免将 stdin 的输入大量储存在内存里,否则会严重降低性能。
- Streaming 不会帮你分割 key 和 value 传进来,传进来的只是一个个字符串而已,需要你自己在代码里手动调用 split()。
- 从 stdin 得到的每一行数据末尾似乎会有 '\n' ,保险起见一般都需要用 rstrip() 来去掉。
- 在想获得 key-value list 而不是一个个处理 key-value pair 时,可以使用 groupby 配合 itemgetter 将 key 相同的 key-value pair 组成一个个 group,得到类似 Java 编写的 reduce 可以直接获取一个 Text 类型的 key 和一个 iterable 作为 value 的效果。注意 itemgetter 的效率比 lambda 表达式的效率要高,所以用 itemgetter 比较好。
1、Python编写的MapReduce程序
1.1、准备输入语料集
若是英文,需要以一定分隔符分割;
若是中文,则需要分词,可以使用中文分词(Chinese Word Segmentation),如开源的jieba分词,NLPIR,StanfordNLP等
我们使用的语料集合input.txt如下(ps. 两个字符之间用空格进行分割):
张三 foo foo 人民 quux 岁月 labs 十九 foo 小猫 bar 威武 quux 极了 abc 我 爱 张三 bar see 洪水 竹席 you by test welcome test
abc labs foo me python hadoop ab ac bc bec 张三 自强 python
1.2、把文本数据放入fs建立好的1025_input文件夹中
localhost:map_reduce_program a6$ hadoop fs -mkdir /1025_input
localhost:map_reduce_program a6$ hadoop fs -put input.txt /1025_input
2、MapReduce程序编写
2.1、map阶段:统计每个单词在input.txt的文档里出现情况
Map函数代码,它会从标准输入(stdin)读取数据,默认以空格分割单词,然后按行输出单词机器出现频率到标准输出(stdout),不过整个Map处理过程并不会统计每个单词出现的总次数,而是直接输出“word,1”,以便作为Reduce的输入进行统计,要求mapper.py具备执行权限。
mapper.py代码如下:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
'''
Test by yyz
Date: 2017/10/24
'''
import sys
# 输入为标准输入stdin
for line in sys.stdin:
'''
# 本地编写测试代码
#if True:
# line="foo foo quux labs foo bar quux abc bar see you by test welcome test"
'''
# 删除开头和结尾的空行
line = line.strip()
# 以默认空格分隔单词到words列表
words = line.split()
for word in words:
# 输出所有单词,格式为“单词,1”以便作为Reduce的输入
print '%s\t%s' % (word, 1)
2.2、reduce阶段:汇总每个单词在input.txt的文档里出现情况
Reduce代码,它会从标准输入(stdin)读取mapper.py的结果,然后统计每个单词出现的总次数并输出到标准输出(stdout),要求reducer.py同样具备可执行 权限。
reducer.py代码如下:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
'''
Test by yyz
Date: 2017/10/24
'''
import sys
current_word = None
current_count = 0
word = None
# 获取标准输入,即mapper.py的标准输出
for line in sys.stdin:
'''
# 本地编写测试代码
#for line in ['welcome 1','welcome 2']:
'''
# 删除开头和结尾的空行
line = line.strip()
# 解析mapper.py输出作为程序的输入,以tab作为分隔符
word,count = line.split('\t', 1)
# 转换count从字符型到整型
try:
count = int(count)
except ValueError:
# count非数字时,忽略此行
continue
# 要求mapper.py的输出做排序(sort)操作,以便对连续的word做判断
if current_word == word:
current_count += count
#print "22222",current_count,current_word
else:
if current_word:
# 输出当前word统计结果到标准输出
print '%s\t%s' % (current_word, current_count)
current_count = count
current_word = word
# 输出最后一个word统计
if current_word == word:
print '%s\t%s' % (current_word, current_count)
3、运行Python编写的MapReduce程序
3.1、在Hadoop平台运行前进行本地测试
mapper阶段执行结果如下
localhost:map_reduce_program a6$ cat input.txt | python mapper.py
张三 1
foo 1
foo 1
人民 1
quux 1
岁月 1
labs 1
十九 1
foo 1
小猫 1
bar 1
威武 1
quux 1
极了 1
abc 1
我 1
爱 1
张三 1
bar 1
see 1
洪水 1
竹席 1
you 1
by 1
test 1
welcome 1
test 1
abc 1
labs 1
foo 1
me 1
python 1
hadoop 1
ab 1
ac 1
bc 1
bec 1
张三 1
自强 1
python 1
3.1.1、本地测试的map和reduce之间没有sort过程的错误运行结果:
localhost:map_reduce_program a6$ cat input.txt | python mapper.py | python reducer.py
张三 1
foo 2
人民 1
quux 1
岁月 1
labs 1
十九 1
foo 1
小猫 1
bar 1
威武 1
quux 1
极了 1
abc 1
我 1
爱 1
张三 1
bar 1
see 1
洪水 1
竹席 1
you 1
by 1
test 1
welcome 1
test 1
abc 1
labs 1
foo 1
me 1
python 1
hadoop 1
ab 1
ac 1
bc 1
bec 1
张三 1
自强 1
python 1
3.1.2、本地调试注意事项:
本地调试用于 Hadoop Streaming 的 Python 程序的基本模式是:
$ cat <input path> | python <path to mapper script> | sort -t $'\t' -k1,1 | python <path to reducer script> > <output path>
这里有几点需要注意:
-
Hadoop 默认按照 tab 来分割 key 和 value,以第一个分割出的部分为 key,按 key 进行排序,因此这里使用 sort -t $'\t' -k1,1 来模拟。如果有其他需求,在交给 Hadoop Streaming 执行时可以通过命令行参数设置,本地调试也可以进行相应的调整,主要是调整 sort 的参数。
-
如果在 Python 脚本里加上了 shebang,并且为它们添加了执行权限,也可以用类似于 ./mapper.py (会根据 shebang 自动调用指定的解释器来执行文件)来代替 python mapper.py。
3.1.3、本地测试的map和reduce之间加上sort过程的正确运行结果
localhost:map_reduce_program a6$ cat input.txt | python mapper.py | sort | python reducer.py
ab 1
abc 2
ac 1
bar 2
bc 1
bec 1
by 1
foo 4
hadoop 1
labs 2
me 1
python 2
quux 2
see 1
test 2
welcome 1
you 1
人民 1
十九 1
威武 1
小猫 1
岁月 1
张三 3
我 1
极了 1
洪水 1
爱 1
竹席 1
自强 1
3.2、在Hadoop平台运行
执行MapReduce任务,输出结果文件指定为 /1025_output
为确保该目录不存在,先进性如下操作
localhost:experiment_data a6$ hadoop fs -test -e /1025_output
localhost:experiment_data a6$ hadoop fs -rmr /1025_output
3.2.1、执行MapReduce的操作如下:
hadoop jar /Users/a6/Applications/hadoop-2.6.5/share/hadoop/tools/lib/hadoop-streaming-2.6.5.jar \
-mapper "python mapper.py" \
-reducer "python reducer.py" \
-input "/1025_input/*" \
-output "/1025_output" \
-file "/Users/a6/Downloads/PycharmProjects/map_reduce_program/mapper.py" \
-file "/Users/a6/Downloads/PycharmProjects/map_reduce_program/reducer.py"
3.2.2、该命令参数解释如下:
/usr/local/hadoop-2.6.4/bin/hadoop jar /usr/local/hadoop-2.6.4/share/hadoop/tools/lib/hadoop-streaming-2.6.4.jar \
-input <输入目录> \ # 可以指定多个输入路径,例如:-input '/user/foo/dir1' -input '/user/foo/dir2'
-inputformat <输入格式 JavaClassName> \
-output <输出目录> \
-outputformat <输出格式 JavaClassName> \
-mapper <mapper executable or JavaClassName> \
-reducer <reducer executable or JavaClassName> \
-combiner <combiner executable or JavaClassName> \
-partitioner <JavaClassName> \
-cmdenv <name=value> \ # 可以传递环境变量,可以当作参数传入到任务中,可以配置多个
-file <依赖的文件> \ # 配置文件,字典等依赖
-D <name=value> \ # 作业的属性配置
3.2.3、查看结果数据
1)、查看生成的分析结果文件清单,其中/1025_output/part-00000为分析结果文件
localhost:experiment_data a6$ hadoop dfs -ls /1025_output
DEPRECATED: Use of this script to execute hdfs command is deprecated.
Instead use the hdfs command for it.
17/10/24 18:49:43 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
Found 2 items
-rw-r--r-- 1 a6 supergroup 0 2017-10-24 18:48 /1025_output/_SUCCESS
2)、查看结果数据
localhost:experiment_data a6$ hadoop fs -cat /1025_output/part-00000
17/10/24 18:51:38 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
ab 1
abc 2
ac 1
bar 2
bc 1
bec 1
by 1
foo 4
hadoop 1
labs 2
me 1
python 2
quux 2
see 1
test 2
welcome 1
you 1
人民 1
十九 1
威武 1
小猫 1
岁月 1
张三 3
我 1
极了 1
洪水 1
爱 1
竹席 1
自强 1