Python+Hadoop Streaming实现MapReduce(word count)

MapReduce示例:WordCount(Java原生)

WordCount是hadoop最经典的一个词频统计方法,它很好的体现了MapReducede分合的思想,在集群中该方法的触发指令为:

$hadoop jar xxx/xxx/wordcount.jarwordcount input_path output_path 其中:

·wordcount:为触发的方法名称

·input_path:指定所要统计的数据在hdfs中存放的位置

·output_path:指定对统计后的结果在hdfs中的存放位置

代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import java.io.IOException;
import java.util.StringTokenizer;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.*;
import org.apache.hadoop.mapreduce.lib.output.*;
public class wordcount {
   
    //继承Mapper,对数据进行拆分,其中<object,text>为数据的输入类型,<text,intwritable>为数据的输出类型
  public static class TokenizerMapper extends Mapper<object,text,text,intwritable>{
    private final static IntWritable one = new IntWritable( 1 );
    private Text word = new Text();
    //map方法的重写,将数据拆分成<word,one>的形式,将数据以<text,intwritable>的形式传送到reduce
    public void map(Object key, Text value, Context context) throws IOException, InterruptedException {
    StringTokenizer it = new StringTokenizer(value.toString());
    while (it.hasMoreTokens()){
     word.set(it.nextToken());
     context.write(word, one);
    }
   }
  }
  //继承Reducer,通过shuffle阶段获得Map处理后的<word,one>的值,对数据进行汇总合并后以<word,result>的形式输出
  //其中<text, intwritable="">为输入的格式,<text,intwritable>为输出的格式
  public static class IntSumReducer extends Reducer<text, intwritable= "" >{
    private IntWritable result = new IntWritable();
    
    //重写reduce方法,对词频进行统计,其中输入的数据形式为<key,{1,1,1,1}>的形式
    public void reduce(Text key, Iterable<intwritable> values, Context context) throws IOException, InterruptedException {
    int sum = 0 ;
    //将相同的key的value值进行相加,得出词频结果
    for (IntWritable val : values){
     sum = sum + val.get();
    }
    result.set(sum);
    context.write(key, result);
   }
  }
   
  public static void wordcountMapReduce(Path input,Path output,Configuration conf) throws IOException, InterruptedException, Exception{
   //建立job任务
   Job job = Job.getInstance(conf, "word count" );
   //配置job中的各个类
   job.setJarByClass(wordcount. class );
   job.setMapperClass(TokenizerMapper. class );
   //combine方法是在reduce之前对map处理结果的一个局部汇总,一般有几个map就会有几个combine
   job.setCombinerClass(IntSumReducer. class );
   job.setReducerClass(IntSumReducer. class );
   job.setOutputKeyClass(Text. class );
   job.setOutputValueClass(IntWritable. class );
   FileInputFormat.addInputPath(job,input);
   FileOutputFormat.setOutputPath(job,output);
   //提交任务
   System.exit(job.waitForCompletion( true )? 0 : 1 );
  }
   
  public static void main(String[] arg) throws Exception{
   Configuration conf = new Configuration();
   //从命令行中获取输入输出的路径
   Path input = new Path(arg[ 1 ]);
   Path output = new Path(arg[ 2 ]);
   //执行mapreduce方法
   wordcountMapReduce(input,output,conf);
  }
}
</intwritable></key,{ 1 1 1 1 }></text,></text,intwritable></text,></word,result></word,one></text,intwritable></word,one></object,text,text,intwritable></text,intwritable></object,text>

 

该方法的主要流程为:

·map阶段:将数据进行拆分,拆分成的形式

·combine阶段:对每一个map拆分的数据进行一个局部的reduce操作,一般情况下combine函数和reduce是一样的,也可以根据自己的需要进行编写

·shuffle阶段:将数据从map拉取到reduce中

·reduce阶段:对所有的输入数据进行合并统计

 

MapReduce示例:WordCount(hadoop streaming)

Hadoop Streaming简单的来说,就是Hadoop提供了Streaming的方法,可以用除java以外的其他语言来编写Mapeduce,也就是说,我们可以用除java以外的其他语言编写一个MapReduce方法将其传给Streaming程序,该程序可以创建一个MR任务提交给Hadoop处理。

用python实现的WordCount程序,在集群中的触发指令为:

$hadoop jar xxx/xxx/hadoop-streaming.jar -mapper 'python xx/xx/map.py'

-filexx/xx/map.py#指定map.py执行文件的位置

-reducer 'python xx/xx/reduce.py'

-filexx/xx/reduce.py#指定reduce.py执行文件的位置

-input input_path#指定输入文件路径

-outputoutput_path#指定输出文件路径

-jobconf mapred.reduce.tasks=20#根据情况来指定reduce的个数

#注意:在'-mapper '和'-reducer '参数设定中一定要加上python,表示该文件用python编译执行,否则会出现java.lang.RuntimeException:PipeMapRed.waitOutputThreads()错误。

map.py程序

 

 

?
1
2
3
4
5
6
7
8
9
10
11
import sys
   
#其中sys.stdin为标准输入
for line in sys.stdin:
   #对输入的文件中的数据进行拆分
   line = line.strip()
   words = line.split()
   for word in words:
     #将数据以<key,value>的形式输出
     print '%s\t%s' %(word, 1 )
</key,value>

reduce.py程序

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import sys
  
#设置并初始化变量
current_word = None
current_count = 0
word = None
  
#获取map输出的<key,value>值
for line in sys.stdin:
   line = line.strip()
   #判断空白行
   if line == '' :
     continue
   else :
     word, count = line.split( '\t' , 1 )
   try :
     #将count转换成整型,这里的 try 是判断如果count不是数字,这直接跳过这一行数据
     count = int (count)
   except ValueError:
     continue
  
   #对数据进行统计操作
   if current_word == word:
     current_count = current_count + count
   else :
     if current_word:
       print '%s\t%s' %(current_word, current_count)
     current_word = word
     current_count = count
  
  
if current_word == word:
   print '%s\t%s' %(current_word, current_count)
</key,value>
整体的主要流程和java原生的WordCount是一样的。


1. hadoop本身是用java写的,所以用java写mapreduce是比较合适的,然而hadoop提供了Streaming的方式,让很多语言可以来写mapreduce,下面就介绍如何用python写一个mapreduce程序,我们就从最简单的word count写起吧

2. word count是比较简单的,所以我们直接上代码,

3. map.py

[python]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #!/usr/bin/env python  
  2. # vim: set fileencoding=utf-8  
  3. import sys  
  4.   
  5. def read_from_input(file):  
  6.     for line in file:  
  7.         yield line.split(' ')  
  8.   
  9.   
  10. def main(separator = ' '):  
  11.     data = read_from_input(sys.stdin)  
  12.     for words in data:  
  13.         for word in words:  
  14.             # write to the reduce  
  15.             print '%s%s%d' % (word, '\t'1)  
  16. if __name__ == '__main__':  
  17.     main()  

这个还是比较简单的,输入是从标准输入得到的,最后输出到reduce端是<word, 1>的形式,相当于用java写的那个context.write(key, value)

4. red.py

[python]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #!/usr/bin/env python  
  2. # vim: set fileencoding=utf-8  
  3. import sys  
  4. from itertools import groupby  
  5. from operator import itemgetter  
  6.   
  7.   
  8. def read_from_mapper(file, separator):  
  9.     for line in file:  
  10.         yield line.strip().split(separator, 2)  
  11.   
  12. def main(separator = '\t'):  
  13.     data = read_from_mapper(sys.stdin, separator)  
  14.     for current_word, group in groupby(data, itemgetter(0)):  
  15.         try:  
  16.             total_count = sum(int(count) for current_word, count in group)  
  17.             print "%s%s%d" % (current_word, separator, total_count)  
  18.         except ValueError:  
  19.             pass  
  20.   
  21. if __name__ == '__main__':  
  22.     main()  

reduce的代码还是稍微有点复杂的,主要他不像用java写那么简便,会直接给你生成<word, list>这样的形式,所以我们就必须自己进行group,这里就用到了python的几个module:itertools和operator,既然他不给我们<word, list>的形式,那么我们就取构造这样的形式,首先输入是从map端过来的,是<word'\t'1>这样的形式,我们用yield将他们组装成generator, 然后 for current_word, group in groupby(data, itemgetter(0))是在data中进行group。key呢就是data里面项的第一个item,其实就是我们的word,最后用了一个简单的列表推导式进行统计每个word的个数,最后输出。

5. 我们需要用shell来运行他

[plain]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #!/bin/bash  
  2.   
  3.   
  4. hadoop jar ../hadoop-streaming-2.0.0-mr1-cdh4.7.0.jar \  
  5.         -input /user/upsmart/output/qianjc/python/input \  
  6.         -output /user/upsmart/output/qianjc/python/output \  
  7.         -file map.py \  
  8.         -file red.py \  
  9.         -mapper "python map.py" \  
  10.         -reducer "python red.py" \  
  11.         -jobconf mapred.reduce.tasks=1 \  
  12.         -jobconf mapred.job.name="qianjc_test"  

指定hadoop-streaming的jar包位置,几个参数的解释如下:

-input hdfs的输入位置

-output 结果写入hdfs的位置

-file 加载辞典,其实就是在运行的时候会将这些file拷贝到每个tasktracker的机器上

-mapper map的代码

-reducer reduce的代码

-jobconf mapred.reduce.tasks 设置reduce的个数

-jobconf mapred.job.name 设置job的名字

6. 本文主要讲了如何用python写简单mapreduce, 学会了这个处理一些简单的问题就比较迅速了,毕竟写脚本是比较快的

    其实我们可以不直接在集群中运行,我们可以先看看这2个python写得对不对,我们可以这么测试:

    cat xxxx.txt | ./map.py | sort | ./reduce.py,然后看输出对不对

7. 如果想了解更多hadoop streaming编程可以访问如下链接:

http://dongxicheng.org/mapreduce/hadoop-streaming-programming/

http://dongxicheng.org/mapreduce/hadoop-streaming-advanced-programming/

http://www.michael-noll.com/tutorials/writing-an-hadoop-mapreduce-program-in-python/

http://cs.smith.edu/dftwiki/index.php/Hadoop_Tutorial_2_--_Running_WordCount_in_Python



1. 输入文件: 
姓名 年龄(以'/t’分割) 
eg: 
张三  15 
李四  15 
张三  16 
张三 15 

输出:将姓名和年龄相同的归一,并输出其人数 
eg:上述输入,输出为:

姓名  年龄 人数(以'/t’分割) 
张三 15   2 
李四 15   1 
张三 16   1 

2.  map程序:

 

#include 
#include 
 
using namespace std;
 
int main(int argc, char** argv)
{
    string name,age;
 
    //读入姓名、年龄
    while(cin >> name >> age)
    {
        //输出姓名、年龄、人数
        cout << name << "/t" << age  << "/t" << "1" << endl;
    }
    return 0;
}

 

编译生成可执行程序: 
g++ -o mapper mapper.cpp

3. reducer程序:

#include 
#include 
#include 
 
using namespace std;
 
int main(int argc, char** argv)
{
    string key, value;
    int num;
    
    //个数统计
    mapint> count_stat;
    mapint>::iterator it_count_stat;
    
    //读入数据并插入map
    while(cin >> key >> value >> num)
    {
        string tmp_key = key + "/t" + value;
    
        //插入数据
        it_count_stat = count_stat.find(tmp_key);
        if(it_count_stat != count_stat.end())
        {
            (it_count_stat->second)++;
        }
        else
        {
            count_stat.insert(make_pair(tmp_key, 1));
        }
    }
 
    //输出统计结果
    for(it_count_stat = count_stat.begin(); it_count_stat != count_stat.end(); it_count_stat++)
    {
        cout<first<<"/t"<second<


      
    }
 
    return 0;
}


编译生成可执行程序: 
g++ -o reducer reducer.cpp

 

4. 测试数据:

张三    15
李四    20
张三    15
张三    16

 

5. 单机测试运行:

$ cat test.txt | ./mapper  | ./reducer 
    李四    20    1
    张三    15    2
    张三    16    1

 

6. Hadoop集群运行: 
   以'/t’作为分隔符,并以前两个字段作为key,reduce任务3个,输入命令:

$ hadoop fs -put test.txt /user/test.txt
$ hadoop streaming -D stream.map.output.field.separator='/t' /
    -D stream.num.map.output.key.fields=2 /
    -input /user/test.txt /
    -output /user/tmp_1324 /
    -mapper ./mapper -reducer ./reducer /
    -file mapper -file reducer /
    -jobconf mapred.reduce.tasks=3 /
    -jobconf mapre.job.name="c++_test" 

 

7.查看输出:

$ hadoop fs -cat /user/tmp_1324/part-00000
李四    20      1
张三    16      1
$ hadoop fs -cat /user/part-00001
$ hadoop fs -cat /user/part-00002
张三    15      2

 


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值