揭开Hadoop的面纱

Written by chenxiaochen32 ,SCUTMSTechClub Hadoop Special Interest Group.


一个hadoop集群拥有许多并行的计算机,用以存储和处理大规模数据集,分布式系统是向外扩展的,当客户端计算机发送作业到计算云时,计算云将会把任务划分到多个节点进行计算,然后节点将计算结果返回到主节点进行统计,再把结果输送给客户端。这也同时说明了,hadoop编程里面最核心的两个动作是:将任务分解,将结果统计,就是mapping和reducing。不过庆幸的是,hadoop已经帮我们实现了这两种方法,以至于我们可以不用了解数据分解和传输的细节,我们只需要实现mapper类和reduce类,我们就可以编出一个hadoop程序。

我们就从测试程序的word count 程序开始。hadoop程序是用java写的。

现在我们想要统计在一篇文章中,每个单词出现的次数。按照我们以往的经验,我们会把一篇文章当做字符串读进内存,利用java util包中的tokenize类,把字符串按照空格分割,然后用循环遍历所有的分词,在多重集合wordCount中的相应项加1.最后display()函数打印出wordCount中的所有条目。

 1 define wordCount as Multiset; 
2 for each document in documentSet
3 {
4 T = tokenize(document);//分词
5 for each token in T
6 {
7 wordCount[token]++;//多重集合
8 }
9 }
10 display(wordCount);

根据我们的分布式计算的思想,我们可以让工作分布在多台机器上,每台机器只需处理需要统计的文章的一小断,当所有的机器都完成时,第二个处理阶段将在主节点服务器中合并这些结果。

第一阶段要分布到多台计算机的伪代码:

 1 define wordCount as Multiset; 
2 for each document in documentSubset
3 {
4 T = tokenize(document);
5 for each token in T
6 {
7 wordCount[token] ++;
8 }
9 }
10 sentToSecondPharse(wordCount);//把分到的文章片段的统计结果发送到上一级

 第二阶段的伪代码为:

1 define totalWordCount as Multiset; 
2 for each wordCount received from firstPharse
3 {
4 multisetAdd(totalWordCount,wordCount);//把各个节点传送过来的片段统计合并为totalWordCount
5}

在MapReduce框架中编写应用程序就是定制化mapper和reducer的过程,他们使用列表和键/值对作为主要的元数据,键与值通常为整数或者字符串,也可以为可忽略的假值,或者是复杂的对象类型。这样说可能有点抽象,我举个实际的例子:

如果我们有两个文件要进行单词统计,那么map函数的输入是list(<String filename,String file_content>),就是一个列表,列表里面包含了两个键值对,一个是<file1,file1_content>,一个是<file2,file2_content>,当我们这台计算机通过这我们先前编写的map函数对这两个文件进行分析处理后,会返回输出list(<String word,int n>),每个单词是键,其出现的次数是值,就是一个类似于<"a",3> <"am",2> <"you",5>这样的键值对列表,或者是<"a",1> <"a",1> <"a",1> <"am",1> <"am",1>..的列表。这样所有的输出都会被聚合到一个包含<String word,int n>的巨大列表中,当所有节点的数据统计结果返回到主节点的时候,系统会让相同的word组织起来形成一个新的键值对<String word,list(int)>,框架让reduce来分别处理每一个<String word,list(int)>,我们只需要在reduce函数中实现把list(int)列表中的全部数字相加就行了,reduce也会返回一个结果,list<String word,int n>;这样我们就完成了统计。

  输入 输出
map <k1,v1> list(<k2,v2>)
reduce <k2,list(v2)> list(<k3,v3>)


单词统计中map和reduce函数的伪代码:

 1 map(String filename,String document) 
2 {
3 List<String> T = tokenize(document);
4 for each token in T
5 {
6 emit ((String)token,(Integer) 1);//逐个生成列表中的元素
7 }
8 }
9 reduce(String token,List<Integer> values)
10 {
11 Integer sum = 0;
12 for each value in values
13 {
14 sum = sum + value;
15 }
16 emit ((String) token,(Integer) sum);
17 }

 

我们在框架中实际使用了一个特殊的函数emit()来逐个生成列表中的元素,使我们编程变的更简便。如果第一阶段的数据量巨大,虽然我们第一阶段计算机充足,但是当第一阶段的计算机将键值对返回到下一阶段的节点进行统计时,第二阶段单台计算机将变的异常缓慢,所以我们按照分布式的思想写了第二阶段,让其可以通过更多台的计算机来实现扩展,所以在上面的程序中我们在reduce中也使用了emit生成键值对,下一阶段的计算机集群将能扑捉到这些键值对进行进一步统计。

好,我们现在可以看看hadoop里面wordCount.java里面的真面目了,下面代码去掉了WordCount.java程序的注释和支撑性代码。

 

 

 1 public class WordCount extends Configured implements Tool 
2 {
3 public static class MapClass extends MapReduceBase implements Mapper<LongWritable,Text,Text,IntWritable>
4 {
5 private final static IntWritable one = new IntWritable(1);
6 private Text word = new Text();
7 //一次处理一行文本
8 public void map (LongWritable key,Text value,OutputCollect<Text,IntWritable>output,Reporter reporter)throws IOException
9 {
10 String line = value.toString;
11 StringTokenizer itr = new StringTokenizer(line);//使用空格进行分词
12 while(itr.hasMoreTokens())//把token放入Text对象中
13 {
14 word.set(itr.nextToken())
15 output.collect(word,one);
16 }
17 }
18 }
19 public static class Reduce extends MapReduceBase implements Reducer<Text,IntWritable,Text,IntWritable>
20 {
21 public void reduce(Text key,Iterator<IntWritable>values,OutputCollector<Text,IntWritable> output,Reporter reporter) throws IOException
22 {
23 int sum = 0;
24 while(values.hasNext())
25 {
26 sum += values.next().get();
27 }
28 output.collect(key,new IntWritable(sum));//输出每个Token的统计结果
29 }
30 }
31 ...
32 }

相信有了前面的理解,对于上面这段代码大家都可以看个大概,甚至可以动手修改了,hadoop不难,不是么。
欢迎大家关注华工微软俱乐部云计算兴趣小组 http://www.cnblogs.com/hadoopSIG/

 

posted @ 2012-04-07 11:59  华工微软俱乐部科技部  阅读(1560)  评论(0编辑  收藏  举报