「这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战」。
hadoop 在设计 mapreduce 框架时,开放了一个用于其它程序编程的接口,也就是hadoop streaming。
它采取一种类似 linux 管道操作的方式处理数据。把Map Reduce这两个部分拆了出来,由我们指定的可执行程序处理。
在这个流程下,对于 map 操作来说,待处理文件将被框架读取后,每个记录会被送到map处理。
在map编程中,读取记录应该以行 为单位从标准输入 中读取。当处理完成之后,对于每个记录,在默认的情况下,应该以制表符 \t
划分一条记录的键和值,并以换行 结尾,输出到标准输出,用于让框架识别。如果不存在\t
,则框架会认为整个一行都是键,而值为空。当然键值的划分是可以通过配置更改的。
reduce操作中的流程与map类似,从标准输入中逐行读取,再经过某些处理之后,写到标准输出。与map类似,也就意味着它与Java原生的API不完全一样,具体在于,reducer中原本封装到迭代器的value-list,需要手动遍历了。
举个例子:
原先的操作可以认为是这样的
AAAAA
BBBB
CCCCC
DDDDDD
reduce 每次操作类似这一行的数据。而再 hadoop streaming 中,能处理的数据可以认为是被展平的列表(尽管它原本就是这样,知识Java的API封装让人从感性上认为它是一个类列表结构)。
reduce 处理的数据类似下面这样:
A
A
B
C
每个组内使连续的,这个使框架保证的,对组的概念的处理需要自行实现。(也就类似先前提过的有些有序序列分组)。
为了演示,这里编写一个经典的词频统计的例子。
在mapper中,需要将单词转换为每行一个,先前尝试过 shell 命令多种方式。这里采取awk来实现:
保存为mapper.sh
#!bash
awk '{
for(i=1;i<=NF;++i){
printf("%s\n",$i)
}
}'
reducer中,我们可以使用python的一个神奇的 grouby函数,它可以帮我们把reduce封装成类似Java中的形式,这里不特地指定key了,grouby的第二个参数用于指定key的值。
mapper.py
:
#!python
import sys
from itertools import groupby
for i, v in groupby(sys.stdin):
sys.stdout.write(f"{i}\t{sum(1 for _ in v)}\n")
准备点输入输出的文件,我们可以向下面这样使用hadoop streaming
yarn jar xxxxx \ #xxxxx指你的streaming包
-input xx -output xx \ # 这两个是输入输入输出文件夹。
-mapper "sh mapper.sh"
-reducer "python reducer.py"
-file mapper.sh -file reducer.py # 用于分发文件到集群中的其他机器。
之后我们就可以在输出文件中看到舍弃难用的javaAPI之后使用mapreduce运算得到的文件。
为什么要写文件,而不是直接在字符串中输入shell命令?这是因为shell命令中许多字符需要转义才能正确的传输。另外,直接使用的shell命令无法使用管道,原因暂时不明。但是脚本文件中却可以使用管道。
分布式环境中,其他机器是没有上述的脚本文件的,因此需要我们传输到其他机器上,-file是streaming接口提供的便捷传输方式。
最后是总结,hadoop streaming 开放的接口,让我们能使用任意的可以与标准输入输出交互的程序都可以用于编写处理任务。而与直接使用可执行程序不同,我们交出了程序的控制权,让框架来自行运行,以实现并行性。