Hadoop streaming:使用原生python编写MapReduce来统计文本文件中所有单词出现的频率功能

6 篇文章 0 订阅
2 篇文章 0 订阅

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>

这里有几点需要注意:

  1. Hadoop 默认按照 tab 来分割 key 和 value,以第一个分割出的部分为 key,按 key 进行排序,因此这里使用 sort -t $'\t' -k1,1 来模拟。如果有其他需求,在交给 Hadoop Streaming 执行时可以通过命令行参数设置,本地调试也可以进行相应的调整,主要是调整 sort 的参数。

  2. 如果在 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

参考:Hadoop Streaming 使用及参数设置

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值