文章目录
Hadoop Streaming
Hadoop streaming is a utility that comes with the Hadoop distribution. The utility allows you to create and run Map/Reduce jobs with any executable or script as the mapper and/or the reducer
Hadoop streaming是一个Hadoop自带的工具,可以允许用户用任何可执行文件或脚本来作为mapper/reducer进而创建和运行MapReduce作业。例如
hadoop jar hadoop-streaming-2.6.5.jar -input /user/root/input1/int.txt -output /user/root/streaming_out -mapper /bin/cat -reducer /usr/bin/wc
How Streaming Works
上面的例子中,mapper/reducer都是可执行文件,从标准输入(stdin
)一行行读取数据,通过标准输出(stdout
)输出数据。这里是和Java的MapReduce完全不同的地方,mapper和reducer都是从标准输入一行行读取,而不是像map方法有明显的key/value,也不是像reduce方法有明显的<key, list< value >>。
该工具会创建一个MapReduce job,提交job到集群上并且监控job状态信息和进度直到job完成。也可以通过 -background
来设置提交job后就立刻返回,不用监听job的进度。
当一个可执行文件作为mapper时,mapper初始化时,每一个mapper任务会把该可执行文件作为一个单独进程启动。mapper任务运行时,它把输入按行划分并把每一行提供给该可执行文件进程的标准输入。
同时,mapper收集可执行文件进程标准输出的内容,并把收到的每一行内容转化成key/value对来作为mapper的输出。 默认情况下,一行中第一个tab之前的部分作为key,之后的(不包括tab)作为value。如果没有tab,整行作为key值,value值为null。然后也可以通过 -inputformat
来自定义输入格式,后面再讨论。
reducer的情况和mapper的过程是类似的,可以通过 -outputformat
来自定义输出格式,后面再讨论。
以上是Map/Reduce框架和streaming mapper/reducer之间的通信协议的基础。
用户可以设置 stream.non.zero.exit.is.failure
来定义一个以非0码退出的streaming task是成功还是失败的。默认设置为true,即退出码非0表示streaming task失败。
Streaming Command Options
Streaming既支持streaming command
,也支持generic command
。通用的命令行格式如下
hadoop command [genericOptions] [streamingOptions]
注意genericOptions
一定要放在streamingOptions
之前,否则任务会失败。
streaming支持如下streamingOptions
配置,也可通过hadoop jar hadoop-streaming-2.6.5.jar -info
获取更加详细的信息
Parameter | Optional/Required | Description |
---|---|---|
-input directoryname or filename | Required | job的输入,可以指定多个-input |
-output directoryname | Required | job的输出 |
-mapper executable or JavaClassName | Required | Mapper 可执行文件 |
-reducer executable or JavaClassName | Required | Reducer 可执行文件 |
-file filename | Optional | 让mapper, reducer, or combiner 可执行文件能在计算节点上得到,即分发可执行文件到计算节点。已过时,可用genericOptions 的-files来代替 |
-inputformat JavaClassName | Optional | 自定义的Inputformat,但是需要返回Text 类型的<k,v>对. TextInputFormat 是默认的Inputformat |
-outputformat JavaClassName | Optional | 自定义的Outputformat需要处理输入类型为Text的<key, (list of values)> . TextOutputformat 是默认的Outputformat |
-partitioner JavaClassName | Optional | 和Partitioner组件作用一样,决定key被送到哪个reduce去处理 |
-combiner streamingCommand or JavaClassName | Optional | 处理map输出的Combiner 可执行文件,combiner能有效地降低带宽即提高shuffle效率,但是combiner不能影响最终业务,如求中位数就不能用combiner。如数据为1 2 3 | 3 4 |
-cmdenv name=value | Optional | 传递环境变量给streaming commands |
-inputreader | Optional | For backwards-compatibility: specifies a record reader class (instead of an input format class) |
-verbose | Optional | Verbose output |
-lazyOutput | Optional | 延迟创建输出目录,只有在reduce 调用context.write 方法时才开始创建 |
-numReduceTasks | Optional | 确定reduce task的数量 |
-mapdebug | Optional | map task失败时调用的脚本(用于调试,参考MapReduce Tutorial的Debugging部分) |
-reducedebug | Optional | reduce task失败时调用的脚本,同上 |
Generic Command Options
Parameter | Optional/Required | Description |
---|---|---|
-conf configuration_file | Optional | 确定application配置文件 |
-D property=value | Optional | 可以修改参数如-D mapreduce.job.reduces=0 |
-fs host:port or local | Optional | 确定namenode |
-jt host:port or local | Optional | 确定resourcemanager |
-files | Optional | 逗号分割的文件,会拷贝到Map/Reduce集群 |
-libjars | Optional | 逗号分割的jar文件会添加到classpath |
-archives | Optional | 逗号分割的archives会被解压到各个计算节点 |
Specifying Map-Only Jobs
如果一个job只需要map不需要reduce可以通过 -D mapreduce.job.reduces=0
来设置,该选项也等同于 -reducer NONE
(这是为了向后兼容To be backward compatible)
单词统计的例子
shell版本
map.sh脚本如下
#!/bin/bash
# 从标准输入中一行行读取数据
while read line;
do
# 分割单词
for word in $line
do
¦ # 需要-e来开启输出转义字符
¦ echo -e "$word\t1"
done
done
reduce.sh脚本如下
#!/bin/bash
count=0
started=0
word=""
while read line;
do
# line的形式是 word\t1
# 注意$line要加上双引号, 否则无法识别从map输出过来的TAB字符
# cut 默认就是通过TAB来切割,-f 1就是获取单词 -f 2就是获取单词统计数
newword=`echo -e "$line" | cut -f 1`
newcount=`echo -e "$line" | cut -f 2`
if [ "$word" != "$newword" ];then
¦ # 如果出现了新的单词就输出老单词的统计数, cmd1 && cmd2 意思就是cmd1成功时才会执行cmd2
¦ [ $started -ne 0 ] && echo -e "$word\t$count"
¦ word=$newword
¦ count=$newcount
¦ started=1
else
¦ count=$(($count + $newcount))
fi
done
# 输出最后一个单词统计
echo -e "$word\t$count"
执行命令如下,由于这里是单词统计可以使用-combiner
来提高shuffle效率,减少数据的传输。
hadoop jar hadoop-streaming-2.6.5.jar -files map.sh,reduce.sh -input /user/root/steam/input -output /user/root/steam/output -mapper map.sh -reducer reduce.sh -combiner reduce.sh
Hadoop Steaming的一个优点就是非常容易调试
cat words.txt | ./map.sh | sort | ./reduce.sh
python版本
map.py脚本如下,注意 开头行#!/usr/bin/env python
的#
和!
中间不能有空格,当时为了格式好看,特意加了个空格和下面对齐,结果就是本地调试能成功但是提交到MapReduce框架上就运行失败!!!
#!/usr/bin/env python
# encoding: utf-8
# **********************************************
#
# Filename: map.py
#
# Author: WangTian
# Description: ---
# Create: 2020-02-18 19:16:19
# Last Modified: 2020-02-18 19:16:19
#
# **********************************************
import sys
# 从标准输入中一行行读取数据
for line in sys.stdin:
# 去掉首尾空格
line = line.strip()
# 根据空格划分单词
words = line.split()
for word in words:
# print "{}\t{}".format(word, 1)
print "%s\t%s" % (word, 1)
reduce.py脚本如下,请特别注意我脚本里面的注释方法,原先我是打算存储一个<word,count>的字典然后统计完后再遍历字典,这样不仅极大地浪费存储空间,而且输出的时候就丢失了MapReduce框架给我们输出数据的排序性。
#!/usr/bin/env python
# encoding: utf-8
# **********************************************
#
# Filename: reduce.py
#
# Author: WangTian
# Description: 单词统计的reducer
# Create: 2020-02-18 19:17:58
# Last Modified: 2020-02-18 19:17:58
#
# **********************************************
import sys
# 存储<word,count>的字典 根本不需要, 这样极大地浪费存储空间
word2count = {}
# 需要临时变量存储key即word,也需要临时变量count来统计
word = ""
count = 0
started = 0
# 从标准输入一行行读取数据
for line in sys.stdin:
# 去除首尾空格
line = line.strip()
# 获取单词和单词数
newword, newcount = line.split()
if word != newword:
if started == 1:
# 打印上一轮的单词统计
print "{}\t{}".format(word, count)
word = newword
count = int(newcount)
started = 1
else:
count = count + int(newcount)
# word2count[word] = word2count.get(word, 0) + int(count)
print "{}\t{}".format(word, count)
# 不准这样输出, 这样会失去map输出数据的顺序性
# for word, count in word2count.items():
# print "{}\t{}".format(word, count)
执行脚本命令如下,同样添加了 combiner
hadoop jar hadoop-streaming-2.6.5.jar -files map.py,reduce.py -input /user/root/steam/input -output /user/root/steam/output -mapper map.py -reducer reduce.py -combiner reduce.py
调试命令如下 cat words.txt | python map.py | sort | python reduce.py
Customizing How Lines are Split into Key/Value Pairs–自定义分割<k,v>方式
正如上面 How Streaming Works
上提到的,Map/Reduce框架读取mapper的输出时也是一行行读取,框架默认根据tab
符将其分为<k,v>对,tab
符前面的就作为key,tab
符后面的就作为value。
然而你也可以自定义分隔符并且指定从第nth(n >= 1)
分隔符开始分隔。看下面给出的例子:
hadoop jar hadoop-streaming-2.6.5.jar \
-D stream.map.output.field.separator=. \
-D stream.num.map.output.key.fields=4 \
-input myInputDirs \
-output myOutputDir \
-mapper /bin/cat \
-reducer /bin/cat
-D stream.map.output.field.separator=.
就是指定分隔符,-D stream.num.map.output.key.fields=4
就是指定从第4个分隔符开始分隔,前面的是key,后面的是value。如果整行内容没有第4个分隔符,那么整行内容就是key,value是空内容(类似于new Text("")
)。
类似地有如下参数(几乎不用考虑用到)
-D stream.reduce.output.field.separator=SEP
和-D stream.num.reduce.output.fields=NUM
指定reduce的输出的key和value
stream.map.input.field.separator
和stream.reduce.input.field.separator
指定input的输入的key和value。
More Usage Examples
Hadoop Partitioner Class
-D mapreduce.map.output.key.field.separator
用来指定分区时用的分隔符,-D mapreduce.partition.keypartitioner.options=-k1,2
用来指定key被分隔符分隔后前两个作为partition,保证前两个一样的数据会被分配到同一个reduce task里。例子如下
当然必须得添加 -partitioner org.apache.hadoop.mapred.lib.KeyFieldBasedPartitioner
来指明Partitioner。
hadoop jar hadoop-streaming-2.6.5.jar \
-D stream.map.output.field.separator=. \
-D stream.num.map.output.key.fields=4 \
-D mapreduce.map.output.key.field.separator=. \
-D mapreduce.partition.keypartitioner.options=-k1,2 \
-D mapreduce.job.reduces=12 \
-input myInputDirs \
-output myOutputDir \
-mapper /bin/cat \
-reducer /bin/cat \
-partitioner org.apache.hadoop.mapred.lib.KeyFieldBasedPartitioner
这实际上相当于将前两个字段指定为主键,将后两个字段指定为辅键。主键用于分区,主键和辅助键的组合用于排序
假设map的key输出数据如下
11.12.1.2
11.14.2.3
11.11.4.1
11.12.1.1
11.14.2.2
分成了3个reduce,前两个去做partition
11.11.4.1
-----------
11.12.1.2
11.12.1.1
-----------
11.14.2.3
11.14.2.2
Hadoop Comparator Class
通过设置 -D mapreduce.job.output.key.comparator.class=org.apache.hadoop.mapreduce.lib.partition.KeyFieldBasedComparator
来指定Comparator Class,
hadoop jar hadoop-streaming-2.6.5.jar \
-D mapreduce.job.output.key.comparator.class=org.apache.hadoop.mapreduce.lib.partition.KeyFieldBasedComparator \
-D stream.map.output.field.separator=. \
-D stream.num.map.output.key.fields=4 \
-D mapreduce.map.output.key.field.separator=. \
-D mapreduce.partition.keycomparator.options=-k2,2nr \
-D mapreduce.job.reduces=1 \
-input myInputDirs \
-output myOutputDir \
-mapper /bin/cat \
-reducer /bin/cat
-D mapreduce.partition.keycomparator.options=-k2,2nr
表示通过第二个字段进行数字的倒序排序。其中 -n
表示排序是numerical sorting
,-r
表示是倒序
假设map的key输出数据是
11.12.1.2
11.14.2.3
11.11.4.1
11.12.1.1
11.14.2.2
排序后结果如下
11.14.2.3
11.14.2.2
11.12.1.2
11.12.1.1
11.11.4.1
Hadoop Aggregate Package
请参考官网 Aggregate。
Hadoop Field Selection Class
请参考官网 Hadoop Field Selection Class。
总结
hadoop Streaming只是个工具,给予那些用其他语言开发比较简单的MapReduce作业,更加复杂的还是选用Java语言去写MapReduce。