1、概述
Hadoop Streaming是Hadoop提供的一个编程工具,它允许用户使用任何可执行文件或者脚本文件作为Mapper和Reducer,例如:
采用shell脚本语言中的一些命令作为mapper和reducer(cat作为mapper,wc作为reducer)
$HADOOP_HOME/bin/hadoop jar $HADOOP_HOME/hadoop-streaming.jar \
-input myInputDirs \
-output myOutputDir \
-mapper cat \
-reducer wc
本文安排如下,第二节介绍Hadoop Streaming的原理,第三节介绍Hadoop Streaming的使用方法,第四节介绍Hadoop Streaming的程序编写方法,在这一节中,用C++、C、shell脚本 和python实现了WordCount作业,第五节总结了常见的问题。文章最后给出了程序下载地址。(本文内容基于Hadoop-0.20.2版本)
(注:如果你采用的语言为C或者C++,也可以使用Hadoop Pipes,具体可参考这篇文章:Hadoop Pipes编程。)
关于Hadoop Streaming高级编程方法,可参考这篇文章:Hadoop Streaming高级编程。
2、Hadoop Streaming原理
mapper和reducer会从标准输入中读取用户数据,一行一行处理后发送给标准输出。Streaming工具会创建MapReduce作业,发送给各个tasktracker,同时监控整个作业的执行过程。
如果一个文件(可执行或者脚本)作为mapper,mapper初始化时,每一个mapper任务会把该文件作为一个单独进程启动,mapper任务运行时,它把输入切分成行并把每一行提供给可执行文件进程的标准输入。 同时,mapper收集可执行文件进程标准输出的内容,并把收到的每一行内容转化成key/value对,作为mapper的输出。 默认情况下,一行中第一个tab之前的部分作为key,之后的(不包括tab)作为value。如果没有tab,整行作为key值,value值为null。
对于reducer,类似。
以上是Map/Reduce框架和streaming mapper/reducer之间的基本通信协议。
3、Hadoop Streaming用法
Usage: $HADOOP_HOME/bin/hadoop jar \
$HADOOP_HOME/hadoop-streaming.jar [options]
options:
(1)-input:输入文件路径
(2)-output:输出文件路径
(3)-mapper:用户自己写的mapper程序,可以是可执行文件或者脚本
(4)-reducer:用户自己写的reducer程序,可以是可执行文件或者脚本
(5)-file:打包文件到提交的作业中,可以是mapper或者reducer要用的输入文件,如配置文件,字典等。
(6)-partitioner:用户自定义的partitioner程序
(7)-combiner:用户自定义的combiner程序(必须用java实现)
(8)-D:作业的一些属性(以前用的是-jonconf),具体有:
1)mapred.map.tasks:map task数目2)mapred.reduce.tasks:reduce task数目3)stream.map.input.field.separator/stream.map.output.field.separator: map task输入/输出数据的分隔符,默认均为\t。4)stream.num.map.output.key.fields:指定map task输出记录中key所占的域数目5)stream.reduce.input.field.separator/stream.reduce.output.field.separator:reduce task输入/输出数据的分隔符,默认均为\t。6)stream.num.reduce.output.key.fields:指定reduce task输出记录中key所占的域数目另外,Hadoop本身还自带一些好用的Mapper和Reducer:(1) Hadoop聚集功能Aggregate提供一个特殊的reducer类和一个特殊的combiner类,并且有一系列的“聚合器”(例如“sum”,“max”,“min”等)用于聚合一组value的序列。用户可以使用Aggregate定义一个mapper插件类,这个类用于为mapper输入的每个key/value对产生“可聚合项”。Combiner/reducer利用适当的聚合器聚合这些可聚合项。要使用Aggregate,只需指定“-reducer aggregate”。
(2)字段的选取(类似于Unix中的‘cut’)Hadoop的工具类org.apache.hadoop.mapred.lib.FieldSelectionMapReduc帮助用户高效处理文本数据,就像unix中的“cut”工具。工具类中的map函数把输入的key/value对看作字段的列表。 用户可以指定字段的分隔符(默认是tab),可以选择字段列表中任意一段(由列表中一个或多个字段组成)作为map输出的key或者value。 同样,工具类中的reduce函数也把输入的key/value对看作字段的列表,用户可以选取任意一段作为reduce输出的key或value。
4、Mapper和Reducer实现
本节试图用尽可能多的语言编写Mapper和Reducer,包括Java,C,C++,Shell脚本,python等。
由于Hadoop会自动解析数据文件到Mapper或者Reducer的标准输入中,以供它们读取使用,所有应先了解各个语言获取标准输入的方法。
(1) Java语言:
见Hadoop自带例子
(2) C++语言:
1234string key;while(cin>>key){cin>>value;….(3) C语言:
12345charbuffer[BUF_SIZE];while(fgets(buffer, BUF_SIZE - 1, stdin)){intlen =strlen(buffer);…}(4) Shell脚本
用管道
(5) Python脚本
123importsysforlineinsys.stdin:.......为了说明各种语言编写Hadoop Streaming程序的方法,下面以WordCount为例,WordCount作业的主要功能是对用户输入的数据中所有字符串进行计数。
(1)C语言实现
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768//mapper#include <stdio.h>#include <string.h>#include <stdlib.h>#define BUF_SIZE 2048#define DELIM "\n"intmain(intargc,char*argv[]){charbuffer[BUF_SIZE];while(fgets(buffer, BUF_SIZE - 1, stdin)){intlen =strlen(buffer);if(buffer[len-1] =='\n')buffer[len-1] = 0;char*querys = index(buffer,' ');char*query = NULL;if(querys == NULL)continue;querys += 1;/* not to include '\t' */query =strtok(buffer," ");while(query){printf("%s\t1\n", query);query =strtok(NULL," ");}}return0;}//---------------------------------------------------------------------------------------//reducer#include <stdio.h>#include <string.h>#include <stdlib.h>#define BUFFER_SIZE 1024#define DELIM "\t"intmain(intargc,char*argv[]){charstrLastKey[BUFFER_SIZE];charstrLine[BUFFER_SIZE];intcount = 0;*strLastKey ='\0';*strLine ='\0';while(fgets(strLine, BUFFER_SIZE - 1, stdin) ){char*strCurrKey = NULL;char*strCurrNum = NULL;strCurrKey =strtok(strLine, DELIM);strCurrNum =strtok(NULL, DELIM);/* necessary to check error but.... */if( strLastKey[0] =='\0'){strcpy(strLastKey, strCurrKey);}if(strcmp(strCurrKey, strLastKey)){printf("%s\t%d\n", strLastKey, count);count =atoi(strCurrNum);}else{count +=atoi(strCurrNum);}strcpy(strLastKey, strCurrKey);}printf("%s\t%d\n", strLastKey, count);/* flush the count */return0;}(2)C++语言实现
123456789101112131415161718192021222324252627282930313233343536373839404142//mapper#include <stdio.h>#include <string>#include <iostream>usingnamespacestd;intmain(){string key;string value ="1";while(cin>>key){cout<<key<<"\t"<<value<<endl;}return0;}//------------------------------------------------------------------------------------------------------------//reducer#include <string>#include <map>#include <iostream>#include <iterator>usingnamespacestd;intmain(){string key;string value;map<string,int> word2count;map<string,int>::iterator it;while(cin>>key){cin>>value;it = word2count.find(key);if(it != word2count.end()){(it->second)++;}else{word2count.insert(make_pair(key, 1));}}for(it = word2count.begin(); it != word2count.end(); ++it){cout<<it->first<<"\t"<<it->second<<endl;}return0;}(3)shell脚本语言实现
12345$HADOOP_HOME/bin/hadoopjar $HADOOP_HOME/hadoop-streaming.jar \-input myInputDirs \-output myOutputDir \-mappercat\-reducerwc(4)Python脚本语言实现
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556#!/usr/bin/env pythonimportsys# maps words to their countsword2count={}# input comes from STDIN (standard input)forlineinsys.stdin:# remove leading and trailing whitespaceline=line.strip()# split the line into words while removing any empty stringswords=filter(lambdaword: word, line.split())# increase countersforwordinwords:# write the results to STDOUT (standard output);# what we output here will be the input for the# Reduce step, i.e. the input for reducer.py## tab-delimited; the trivial word count is 1'%s\t%s'%(word,1)#---------------------------------------------------------------------------------------------------------#!/usr/bin/env pythonfromoperatorimportitemgetterimportsys# maps words to their countsword2count={}# input comes from STDINforlineinsys.stdin:# remove leading and trailing whitespaceline=line.strip()# parse the input we got from mapper.pyword, count=line.split()# convert count (currently a string) to inttry:count=int(count)word2count[word]=word2count.get(word,0)+countexceptValueError:# count was not a number, so silently# ignore/discard this linepass# sort the words lexigraphically;## this step is NOT required, we just do it so that our# final output will look more like the official Hadoop# word count examplessorted_word2count=sorted(word2count.items(), key=itemgetter(0))# write the results to STDOUT (standard output)forword, countinsorted_word2count:'%s\t%s'%(word, count)5、常见问题
(1)作业总是运行失败:
需要把mapper文件和reducer文件放到各个tasktracker上,保证各个节点均有一份。也可在提交作业时,采用-file选项指定这些文件。
(2)用脚本编写时,第一行需注明脚本解释器,默认是shell
6、参考资料
【1】C++&Python实现Hadoop Streaming的paritioner和模块化
【2】如何在Hadoop中使用Streaming编写MapReduce
7、程序打包下载
文章中用到的程序源代码可在此处下载!
原创文章,转载请注明: 转载自董的博客
本文链接地址: http://dongxicheng.org/mapreduce/hadoop-streaming-programming/
浙公网安备 33010602011771号