技术-MapReduce

1,MapReduce通俗解释方法

http://blog.csdn.net/zhu_yanjie/article/details/7741301

编注:下面这段话是网上其他人用最简短的语言解释MapReduce:
We want to count all the books in the library. You count up shelf #1, I count up shelf #2. That’s map. The more people we get, the faster it goes.
我们要数图书馆中的所有书。你数1号书架,我数2号书架。这就是“Map”。我们人越多,数书就更快。
Now we get together and add our individual counts. That’s reduce.
现在我们到一起,把所有人的统计数加在一起。这就是“Reduce”。

2,MapReduce理论好处

http://book.51cto.com/art/201111/304549.htm

MapReduce也是一个数据处理模型,它最大的优点是容易扩展到多个计算节点上处理数据。在MapReduce模型中,数据处理原语被称为mapper和reducer。分解一个数据处理应用为mapper和reducer有时是繁琐的,但是一旦以MapReduce的形式写好了一个应用程序,仅需修改配置就可以将它扩展到集群中几百、几千,甚至几万台机器上运行。正是这种简单的可扩展性使得MapReduce模型吸引了众多程序员。

http://www.cstor.cn/textdetail.asp?id=1156

在Google,每天有海量的数据需要在有限的时间内进行处理(其实每个互联网公司都会碰到这样的问题),每个程序员都需要进行分布式的程序开发,这其中包括如何分布、调度、监控以及容错等等。Google的MapReduce正是把分布式的业务逻辑从这些复杂的细节中抽象出来,使得没有或者很少并行开发经验的程序员也能进行并行应用程序的开发。
面对复杂问题,古人教导我们要“分而治之”,英文中对应的词是”Divide and Conquer“。Map/Reduce其实就是Divide/Conquer的过程,通过把问题Divide,使这些Divide后的Map运算高度并行,再将Map后的结果Reduce(根据某一个Key),得到最终的结果。
Googler发现这是问题的核心,其它都是共性问题。因此,他们把MapReduce抽象分离出来。这样,Google的程序员可以只关心应用逻辑,关心根据哪些Key把问题进行分解,哪些操作是Map操作,哪些操作是Reduce操作。其它并行计算中的复杂问题诸如分布、工作调度、容错、机器间通信都交给Map/Reduce Framework去做,很大程度上简化了整个编程模型。

MapReduce的另一个特点是,Map和Reduce的输入和输出都是中间临时文件(MapReduce利用Google文件系统来管理和访问这些文件),而不是不同进程间或者不同机器间的其它通信方式。我觉得,这是Google一贯的风格,化繁为简,返璞归真。

3,MapReduce实际流程

3.1 MR处理大数据集的过程

Hadoop的MapReduce过程具有如下形式:
1) map:    (K1, V1)       => list(K2, V2)
2) reduce: (K2, list(V2)) => list(K3, V3)
更详细的:
1) map:       (K1, V1)       –> list (K2, V2)              
2) combine:   (K2, list(V2)) –> list(K2, V2)
3) partition: (K2, V2)       –> integer              
4) reduce:    (K2, list(V2)) –> list(K3, V3) 



3.2 WordCount执行步骤

Hadoop将Job分成task进行处理,共两种task:map task和reduce task
Hadoop有两类的节点控制job的运行: JobTrackerTaskTracker
JobTracker协调整个job的运行,将task分配到不同的TaskTracker上
TaskTracker负责运行task,并将结果返回给JobTracker

1)将文件拆分成splits:
Hadoop将输入数据分成固定大小的块,我们称之input split
Hadoop为每一个input split创建一个map task,在此task中依次处理此split中的一个个记录(record)
Hadoop会尽量让输入数据块所在的DataNode和task所执行的DataNode(每个DataNode上都有一个TaskTracker)为同一个,可以提高运行效率,所以input split的大小也一般是HDFS的block的大小。
由于测试用的文件较小,所以每个文件为一个split,并将文件按行分割形成<key,value>对,如图4-1所示。这一步由MapReduce框架自动完成,其中偏移量(即key值)包括了回车所占的字符数(Windows和Linux环境会不同)。

图4-1 分割过程

2)map:
将分割好的<key,value>对交给用户定义的map方法进行处理,生成新的<key,value>对,如图4-2所示。

图4-2 执行map方法

3)sort和combine:
得到map方法输出的<key,value>对后,Mapper会将它们按照key值进行排序,并执行Combine过程,将key至相同value值累加,得到Mapper的最终输出结果。如图4-3所示。

图4-3 Map端排序及Combine过程

4)suffle:
map到指定reduce端过程由shuffle来决定。
hadoop的map/reduce中支持对key进行分区,从而让map出来的数据均匀分布在reduce上,当然,有时候由于机器间配置问题,可能不需要数据均匀,这时候也能派上用场。  框架自带了一个默认的分区类,HashPartitioner,继承它可以自定义key分区。 
public class HashPartitioner<K, V> extends Partitioner<K, V> {
  public int getPartition(K key, V value, int numReduceTasks) {
    return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;
  }
}
代码中:
(key.hashCode() & Integer.MAX_VALUE) % numReduceTasks; 
将key均匀分布在ReduceTasks上,举例如果Key为Text的话,Text的hashcode方法跟String的基本一致,都是采用的Horner公式计算,得到一个int,string太大的话这个int值可能会溢出变成负数,所以“与”上Integer.MAX_VALUE(即0111111111111111),然后再对reduce个数取余,这样就可以让key均匀分布在reduce上。 
注:这个简单算法得到的结果可能不均匀,因为key毕竟不会那么线性连续,这时候可以自己写个测试类,计算出最优的hash算法。 

在reduce中,相同key的所有的记录一定会到同一个TaskTracker上面运行,然而不同的key可以在不同的TaskTracker上面运行,我们称之为partition
partition的规则为:(K2, V2) –> Integer, 也即根据K2,生成一个partition的id,具有相同id的K2则进入同一个partition,被同一个TaskTracker上被同一个Reducer进行处理。

5)reduce:
Reducer先对从Mapper接收的数据进行排序,再交由用户自定义的reduce方法进行处理,得到新的<key,value>对,并作为WordCount的输出结果,如图4-4所示。

图4-4 Reduce端排序及输出结果

6,整个流程如下:


3.2 数据类型

Hadoop提供了如下内容的数据类型,这些数据类型都实现了WritableComparable接口,以便用这些类型定义的数据可以被序列化进行网络传输和文件存储,以及进行大小比较。
BooleanWritable:标准布尔型数值
ByteWritable:单字节数值
DoubleWritable:双字节数
FloatWritable:浮点数
IntWritable:整型数
LongWritable:长整型数
Text:使用UTF8格式存储的文本
NullWritable:当<key,value>中的key或value为空时使用

3.3 InputFormat和InputSplit

(1)OutputFormat
InputSplit是Hadoop定义的用来传送给每个单独的map的数据,InputSplit存储的并非数据本身,而是一个分片长度和一个记录数据位置的数组。生成InputSplit的方法可以通过 InputFormat()来设置。
当数据传送给map时,map会将输入分片传送到InputFormat,InputFormat则调用方法getRecordReader()生成RecordReader,RecordReader再通过creatKey()、creatValue()方法创建可供map处理的<key,value>对。简而言之, InputFormat()方法是用来生成可供map处理的<key,value>对的。
Hadoop预定义了多种方法将不同类型的输入数据转化为map能够处理的<key,value>对,它们都继承自InputFormat。

其中TextInputFormat是Hadoop默认的输入方法,在TextInputFormat中,每个文件(或其一部分)都会单独地作为map的输入,而这个是继承自FileInputFormat的。之后,每行数据都会生成一条记录,每条记录则表示成<key,value>形式。

key值是每个数据的记录在数据分片中字节偏移量,数据类型是LongWritable;  
value值是每行的内容,数据类型是Text。
(2)OutputFormat
每一种输入格式都有一种输出格式与其对应。默认的输出格式是TextOutputFormat,这种输出方式与输入类似,会将每条记录以一行的形式存入文本文件。不过,它的键和值可以是任意形式的,因为程序内容会调用toString()方法将键和值转换为String类型再输出。


3.4  WordCount代码

WordCount.java

import java.io.IOException;   
import java.util.Iterator;   
import java.util.StringTokenizer;    
import org.apache.hadoop.fs.Path;   
import org.apache.hadoop.io.IntWritable;   
import org.apache.hadoop.io.LongWritable;   
import org.apache.hadoop.io.Text;   
import org.apache.hadoop.mapred.FileInputFormat;   
import org.apache.hadoop.mapred.FileOutputFormat;   
import org.apache.hadoop.mapred.JobClient;   
import org.apache.hadoop.mapred.JobConf;   
import org.apache.hadoop.mapred.MapReduceBase;   
import org.apache.hadoop.mapred.Mapper;   
import org.apache.hadoop.mapred.OutputCollector;   
import org.apache.hadoop.mapred.Reducer;   
import org.apache.hadoop.mapred.Reporter;   
import org.apache.hadoop.mapred.TextInputFormat;   
import org.apache.hadoop.mapred.TextOutputFormat;   
/**  
 * 描述:WordCount explains by Felix  
 * @author Hadoop Dev Group  
*/   public class WordCount   
{    
    /**  
     * MapReduceBase类:实现了Mapper和Reducer接口的基类(其中的方法只是实现接口,而未作任何事情)  
     * Mapper接口:  
     * WritableComparable接口:实现WritableComparable的类可以相互比较。所有被用作key的类应该实现此接口。  
     * Reporter 则可用于报告整个应用的运行进度,本例中未使用。   
     *   
     */  
    public static class Map extends MapReduceBase implements  
            Mapper<LongWritable, Text, Text, IntWritable>        //设定了map函数输入的形式为longwritable<key>text<value>输出地形式为text<key> intwritable<value>
    {   
    /**  
     * LongWritable, IntWritable, Text 均是 Hadoop 中实现的用于封装 Java 数据类型的类,这些类实现了WritableComparable接口,  
     * 都能够被串行化从而便于在分布式环境中进行数据交换,你可以将它们分别视为long,int,String 的替代品。        */  
     private final static IntWritable one = new IntWritable(1);   //定义一个intwritable型的常量,用来说明出现过一次
     private Text word = new Text();                              //定义一个text型的变量,用来保存单词  
       
    /**  
     * Mapper接口中的map方法:  
     * void map(K1 key, V1 value, OutputCollector<K2,V2> output, Reporter reporter)  
     * 映射一个单个的输入k/v对到一个中间的k/v对  
     * 输出对不需要和输入对是相同的类型,输入对可以映射到0个或多个输出对。  
     * OutputCollector接口:收集Mapper和Reducer输出的<k,v>对。  
     * OutputCollector接口的collect(k, v)方法:增加一个(k,v)对到output  
     */  
     public void map(LongWritable key, Text value, OutputCollector<Text, IntWritable> output, Reporter reporter)           //map中的参变量说明map输入时的keyvalue对的形式,以及map输出和reduce接收的keyvalue数据类型
          throws IOException   
        {   
           String line = value.toString();   //将输入中的一行保存到line中
           StringTokenizer tokenizer = new StringTokenizer(line);   //将一行保存到准备切词的工具中
           while (tokenizer.hasMoreTokens())   //判断是否到一行的结束
            {   
                word.set(tokenizer.nextToken());  //设定key即word的值为从每一行切下来的单词   
                output.collect(word, one);       //设定map函数输出的keyvalue对
             }   
        }   
    }   
   
public static class Reduce extends MapReduceBase implements  Reducer<Text, IntWritable, Text, IntWritable>   //设定reduce函数中输入对的数据类型是text和intwritable,输出对的数据类型是text和intwritable
    {   
        public void reduce(Text key, Iterator<IntWritable> values,   
                OutputCollector<Text, IntWritable> output,  Reporter reporter)    //设定reduce函数中输入对的数据类型是text和intwritable,输出对的数据类型是text和intwritable
                throws IOException   
        {   
            int sum = 0;   
            while (values.hasNext())        //计算同一个key下,所有value的总和
            {   
               sum += values.next().get();   //获取下一个value的值
            }   
            output.collect(key, new IntWritable(sum));   //收集reduce输出结果
        }   
    }     
    public static void main(String[] args) throws Exception   
    {   
        /**  
         * JobConf:map/reduce的job配置类,向hadoop框架描述map-reduce执行的工作  
         * 构造方法:JobConf()、JobConf(Class exampleClass)、JobConf(Configuration conf)等  
         */  
        JobConf conf = new JobConf(WordCount.class);   
        conf.setJobName("wordcount");           //设置一个用户定义的job名称   
        conf.setOutputKeyClass(Text.class);    //为job的输出数据设置Key类   
        conf.setOutputValueClass(IntWritable.class);   //为job输出设置value类   
        conf.setMapperClass(Map.class);         //为job设置Mapper类   
        conf.setCombinerClass(Reduce.class);      //为job设置Combiner类   
        conf.setReducerClass(Reduce.class);        //为job设置Reduce类   
        conf.setInputFormat(TextInputFormat.class);    //为map-reduce任务设置InputFormat实现类   
        conf.setOutputFormat(TextOutputFormat.class);  //为map-reduce任务设置OutputFormat实现类 
       /**  
         * InputFormat描述map-reduce中对job的输入定义  
         * setInputPaths():为map-reduce job设置路径数组作为输入列表  
         * setInputPath():为map-reduce job设置路径数组作为输出列表  
         */  
        FileInputFormat.setInputPaths(conf, new Path(args[0]));   
        FileOutputFormat.setOutputPath(conf, new Path(args[1]));   
        JobClient.runJob(conf);         //运行一个job   
    }   
}   

4,更详尽探讨参考

http://www.cnblogs.com/biyeymyhjob/category/402609.html




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值