Hadoop Streaming思考总结

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获取更加详细的信息

ParameterOptional/RequiredDescription
-input directoryname or filenameRequiredjob的输入,可以指定多个-input
-output directorynameRequiredjob的输出
-mapper executable or JavaClassNameRequiredMapper 可执行文件
-reducer executable or JavaClassNameRequiredReducer 可执行文件
-file filenameOptional让mapper, reducer, or combiner 可执行文件能在计算节点上得到,即分发可执行文件到计算节点。已过时,可用genericOptions的-files来代替
-inputformat JavaClassNameOptional自定义的Inputformat,但是需要返回Text类型的<k,v>对. TextInputFormat 是默认的Inputformat
-outputformat JavaClassNameOptional自定义的Outputformat需要处理输入类型为Text的<key, (list of values)>. TextOutputformat是默认的Outputformat
-partitioner JavaClassNameOptional和Partitioner组件作用一样,决定key被送到哪个reduce去处理
-combiner streamingCommand or JavaClassNameOptional处理map输出的Combiner 可执行文件,combiner能有效地降低带宽即提高shuffle效率,但是combiner不能影响最终业务,如求中位数就不能用combiner。如数据为1 2 3 | 3 4
-cmdenv name=valueOptional传递环境变量给streaming commands
-inputreaderOptionalFor backwards-compatibility: specifies a record reader class (instead of an input format class)
-verboseOptionalVerbose output
-lazyOutputOptional延迟创建输出目录,只有在reduce调用context.write方法时才开始创建
-numReduceTasksOptional确定reduce task的数量
-mapdebugOptionalmap task失败时调用的脚本(用于调试,参考MapReduce TutorialDebugging部分)
-reducedebugOptionalreduce task失败时调用的脚本,同上

Generic Command Options

ParameterOptional/RequiredDescription
-conf configuration_fileOptional确定application配置文件
-D property=valueOptional可以修改参数如-D mapreduce.job.reduces=0
-fs host:port or localOptional确定namenode
-jt host:port or localOptional确定resourcemanager
-filesOptional逗号分割的文件,会拷贝到Map/Reduce集群
-libjarsOptional逗号分割的jar文件会添加到classpath
-archivesOptional逗号分割的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.separatorstream.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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值