mapreduce(六):MapReduce原理

来源:互联网 发布:淘宝调研网站 编辑:程序博客网 时间:2024/05/20 18:47

MapReduce原理


 
执行步骤:
1、Map任务处理
1.1读取输入文件内容,解析成key、value对,对输入文件的每一行,解析成key、value对。每一个键值对调用一次Map函数。
1.2写自己的逻辑,对输入的key、value处理,转换成新的key、value输出。
1.3对输出的key、value进行分区。
1.4对不同分区的数据,按key进行排序、分组。相同key的value放到一个集合中。
1.5(可选)分组后的数据进行归约。
2、Reduce任务处理
2.1对多个map任务的输出,按照不同的分区,通过网络copy到不同的reduce节点。
2.2对多个map任务的输出进行合并、排序。写reduce函数自己的逻辑,对输入的key、value处理,转换成新的key、value输出。
2.3把reduce的输出保存到文件中
 

MapReduce概述
* MapReduce是一种分布式计算模型,由google提出,主要用于搜索领域,解决海量数据的计算问题。
* MR由两个阶段组成:Map和Reduce,用户只需要实现map()和reduce()两个函数,即可实现分布式计算,非常简单。
* 这两个函数的形参是key、value对,表示函数的输入信息。

 
Hadoop1.0叫JobTracker 和 TaskTracker Hadoop2.0后叫ResourceManager 和 NodeManager

2.MR执行流程
 

 (1).客户端提交一个mr的jar包给JobClient(提交方式:hadoop jar ...)
 (2).JobClient通过RPC和JobTracker进行通信,返回一个存放jar包的地址(HDFS)和jobId
 (3).client将jar包写入到HDFS当中(path = hdfs上的地址 + jobId)
 (4).开始提交任务(任务的描述信息,不是jar, 包括jobid,jar存放的位置,配置信息等等)
 (5).JobTracker进行初始化任务
 (6).读取HDFS上的要处理的文件,开始计算输入分片,每一个分片对应一个MapperTask
 (7).TaskTracker通过心跳机制领取任务(任务的描述信息)
 (8).下载所需的jar,配置文件等
 (9).TaskTracker启动一个java child子进程,用来执行具体的任务(MapperTask或ReducerTask)
 (10).将结果写入到HDFS当中

序列化概念:
* 序列化(Serialization)是指把结构化对象转化为字节流。
* 反序列化(Deserialization)是序列化的逆过程。即把字节流转回结构化对象。
* Java序列化(java.io.Serializable)
Hadoop序列化的特点:
* 序列化格式特点:
1.紧凑:高效使用存储空间。
2.快速:读写数据的额外开销小。
3.可扩展:可透明地读取老格式的数据。
4.互操作性:支持多语言的交互。
Hadoop的序列化格式:Writable


案例1:计算一个文件中单词出现的次数(WorldCount)

1、linux上新建一个words文件,里面保存如下单词。

 将该文件上传到hdfs上


2 在eclipse下新建以下三个类

WCMapper类

 Java Code 
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
package com.hadoop.mr;

import java.io.IOException;

import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

/**Mapper<KEYIN,VALUEIN,KEYOUT,VALUEOUT>**/
public class WCMapper extends Mapper<LongWritable,Text,Text,LongWritable> {

    @Override
    protected void map(LongWritable key, Text value,
            Context context)
            throws IOException, InterruptedException {
         //接收数据V1
         String line = value.toString();
         //切分数据
         String[] words = line.split(" ");
         //循环
         for(String w : words) {
             context.write(new Text(w), new LongWritable(1));
         }
    }
}

WCReducer类

 Java Code 
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
package com.hadoop.mr;

import java.io.IOException;

import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

public class WCReducer extends Reducer<Text,LongWritable,Text,LongWritable>{

    @Override
    protected void reduce(Text key, Iterable<LongWritable> v2s,
            Context context)
            throws IOException, InterruptedException {
        //接收数据
        //定义一个计数器
        long counter = 0;
        //循环v2s
        for(LongWritable i : v2s) {
            counter += i.get();
        }
        //输出
        context.write(key, new LongWritable(counter));
    }
}

WorldCount类

 Java Code 
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
package com.hadoop.mr;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

/**
 * 1.分析具有的业务逻辑,确定输出数据的样式
 * 2.自定义一个类, 这个类要继承org.apache.hadoop.mapreduce.Mapper,
 * 重写map方法,在map方法中实现具体业务逻辑,将新的key、value输出
 * 3.自定义一个类,这个类要继承org.apache.hadoop.mapreduce.Reducer,
 * 重写reduce方法,并实现具体业务逻辑,将新的key、value输出
 * 4.将自定义的mapper和reducer通过job对象组装起来。
 * @author xin,
 *
 */

public class WordCount {
    
    public static void main(String[] args) throws Exception {
        //组装map和reduce
        Job job = Job.getInstance(new Configuration());
        //注意:main方法所在的类设置为参数
        job.setJarByClass(WordCount.class);
        //设置Mapper相关属性
        job.setMapperClass(WCMapper.class);
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(LongWritable.class);
        FileInputFormat.setInputPaths(job, new Path(args[0]));
        //设置reduce相关属性
        job.setReducerClass(WCReducer.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(LongWritable.class);
        FileOutputFormat.setOutputPath(job, new Path(args[1]));
        //true 打印出进度,false不打印出进度
        job.waitForCompletion(true);
    }
}

3、打成jar包,上传到linux 服务器上


4.执行

查看计算结果:



案例2:

手机只要上一次网就会产生一条数据,计算同一手机号在一段时间内的用的总流量(总流量 = 上行流量 + 下行流量)。

这个例子与上个例子相比有以下不同:1、使用了自定义对象序列化;2、将DCMapper和DCReducer作为DataCount的内部类,代码更加简洁紧凑。3、使用了Maven。

其他步骤相似,参看上例.

手机流量相关数据:

项目结构


 Java Code 
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
package com.hadoop.mr.dc;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

import org.apache.hadoop.io.Writable;

/** 实现Writable,用于序列化和反序列化 */
public class DataBean implements Writable {

    // 手机号
    private String telNo;
    // 上行流量
    private long upPayLoad;
    // 下行流量
    private long downPayLoad;
    // 总流量
    private long totalPayLoad;

    // 需要添加无参的构造方法,否则反射时会报错。
    public DataBean() {

    }

    public DataBean(String telNo, long upPayLoad, long downPayLoad) {
        this.telNo = telNo;
        this.upPayLoad = upPayLoad;
        this.downPayLoad = downPayLoad;
        this.totalPayLoad = upPayLoad + downPayLoad;
    }

    public String getTelNo() {
        return telNo;
    }

    public void setTelNo(String telNo) {
        this.telNo = telNo;
    }

    public long getUpPayLoad() {
        return upPayLoad;
    }

    public void setUpPayLoad(long upPayLoad) {
        this.upPayLoad = upPayLoad;
    }

    public long getDownPayLoad() {
        return downPayLoad;
    }

    public void setDownPayLoad(long downPayLoad) {
        this.downPayLoad = downPayLoad;
    }

    public long getTotalPayLoad() {
        return totalPayLoad;
    }

    public void setTotalPayLoad(long totalPayLoad) {
        this.totalPayLoad = totalPayLoad;
    }

    // 反序列化
    public void readFields(DataInput inthrows IOException {
        this.telNo = in.readUTF();
        this.upPayLoad = in.readLong();
        this.downPayLoad = in.readLong();
        this.totalPayLoad = in.readLong();
    }

    // 序列化
    // 注意:1顺序,2类型Mapper<KEYIN,VALUEIN,KEYOUT,VALUEOUT>
    public void write(DataOutput out) throws IOException {
        // String类型序列化用writeUTF
        out.writeUTF(telNo);
        out.writeLong(upPayLoad);
        out.writeLong(downPayLoad);
        out.writeLong(totalPayLoad);
    }

    @Override
    public String toString() {
        return upPayLoad + "\t" + downPayLoad + "\t" + totalPayLoad;
    }

}

 Java Code 
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


package com.hadoop.mr.dc;

import java.io.IOException;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

public class DataCount {
    //入口程序
    public static void main(String[] args) throws Exception {
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf);
        job.setJarByClass(DataCount.class);
        
        job.setMapperClass(DCMapper.class);
        //当Mapper中的输出的key和value的类型和Reduce输出的key和value的类型相同时,以下两句可以省略。
/*      job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(DataBean.class);*/

        FileInputFormat.setInputPaths(job, new Path(args[0]));
        
        job.setReducerClass(DCReducer.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(DataBean.class);
        FileOutputFormat.setOutputPath(job, new Path(args[1]));
        job.waitForCompletion(true);
    }
    
    /**Mapper<KEYIN,VALUEIN,KEYOUT,VALUEOUT>    KEYIN 这边没有用到,所以写LongWritable或者Text都可以,
     *因为处理的是文本, VALUEIN就是文本的一行。输出是按手机号分组,KEYOUT是Text。定义一个DataBean,输出的value保存在DataBean中  **/

    public static class DCMapper extends Mapper<LongWritable,Text,Text,DataBean> {

        @Override
        protected void map(LongWritable key, Text value, Context context)
                throws IOException, InterruptedException {
            String line = value.toString();
            String[] fields = line.split("\t");
            long up = Long.parseLong(fields[8]);
            long down = Long.parseLong(fields[9]);
            context.write(new Text(fields[1]), new DataBean(fields[1], up, down));
        }
        
    }
    
    public static class DCReducer extends Reducer<Text, DataBean,  Text, DataBean> {

        @Override
        protected void reduce(Text keyIn, Iterable<DataBean> valuesIn, Context context)
                throws IOException, InterruptedException {
            long upSum = 0;
            long downSum = 0;
            for(DataBean value : valuesIn) {
                upSum += value.getUpPayLoad();
                downSum += value.getDownPayLoad();
            }
            context.write(keyIn, new DataBean(keyIn.toString(), upSum, downSum));
        }
    }
}
0 0
原创粉丝点击