Hadoop之分布式计算框架 —— MapReduce

一、MapReduce概述


1.1、含义

它将大型数据操作作业分解为可以跨服务器集群并行执行的单个任务,适用于大规模数据处理场景,每个job包含Map和Reduce两部分

1.2、设计思想

•分而治之
简化并行计算的编程模型
•构建抽象模型:Map和Reduce
开发人员专注于实现Mapper和Reducer函数
•隐藏系统层细节
开发人员专注于业务逻辑实现

1.3、核心功能

将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序,并发运行在一个hadoop集群上


二、MapReduce优缺点

2.1、优点

特点 体现
易于编程 用户只需简单的实现一些接口,就可以完成一个分布式程序,这个分布式程序可以分布到大量廉价的 PC 机器运行
高容错性 当一台机器挂了,它可以将计算任务转移到另一个节点运行。从而保证任务不会因此而失败,这个过程是Hadoop内部完成的,不需要人工操作
高吞吐量 适合 PB 级以上海量数据离线处理
可扩展性 当计算机资源不能满足我们的需求时,仅需简单的增加机器就可以扩展计算能力

2.2、缺点

特点 体现
难以实时计算 MapReduce无法像Mysql那样,在毫秒或者秒内返回结果
不适合流式计算 MapReduce本身的设计特点就要求输入的数据是静态的,但流式计算的输入数据是动态的
不适合DAG(有向图)计算 多个应用程序存在依赖关系,后一个应用程序的输入为前一个的输出。因此每个MapReduce 作业的输出结果都会写入到磁盘,会造成大量的磁盘IO,导致性能非常的低下。

三、MapReduce进程及工作机制

一个完整的mapreduce程序在分布式运行时有三类实例进程:

进程 功能
AppMaster 负责整个程序的过程调度及状态协调
MapTask 负责map阶段的整个数据处理流程
ReduceTask 负责reduce阶段的整个数据处理流程

3.1、MapTask工作机制

3.2、Shuffle机制

3.3、ReduceTask工作机制


四、MapReduce编程规范

需要重点明确两点:
1.一个记录调用一次map()方法
2.相同的key调用一次reduce()方法

用户编写的程序分成三个部分:Mapper、Reducer和Driver

  • Mapper类
  1. 用户自定义的Mapper要继承框架提供的Mapper父类
  2. Mapper的输入数据是KV键值对的形式(KV类型可自定义)
  3. 对数据的逻辑处理写在Mapper类中 map() 方法中
  4. Mapper的输出数据是KV键值对的形式(KV类型可自定义)
  5. map() 方法 (maptask进程) 每一个<K,V>数据执行一次
  • Reducer类
  1. 用户自定义的Reducer要继承框架提供的Reducer父类
  2. Reducer的输入数据类型对应Mapper的输出数据类型,也是KV
  3. 对数据的逻辑处理写在Reducer类中 reduce() 方法中
  4. 每一组相同 K 的 <K,Iterator> 组调用一次 reduce() 方法
  • Driver类
    整个程序需要编写一个 Driver 来进行提交,将自定义 Mapper 和 Reducer 类组合成一个 job,并提交 job 对象

五、MapReduce案例WordCount

在这里插入图片描述
Input: 读取文本文件

Splitting: 将文本切片,得到键值对(K1,V1),此时K1是行数,V1是对应行的文本内容

Mapping: 将每一行的文本内容根据空格进行拆分,得到新的键值对(K2,V2),此时K2是单词,V2是对应单词的个数,此时还没有合并,所以V2的值是1

Shuffling: 由于 Mapping 步骤可能是在不同机器上执行的,因此需要通过 Shuffling 将相同key的键值对合并到同一个节点上,GroupComparator 根据相同的K2分组后,有新的键值对(K2,List< V2>),List(V2) 为可迭代集合,V2 就是 Mapping 中的 V2

Reducing: Shuffling 中的键值对组,一组分配一个reducer,对 List< V2> 进行求和,计算出单词出现的总次数

Final Result: 数据结果落盘,我们可以清楚的看到单词及其出现次数
—————————————————————————————————————
代码演示:

测试内容:

Deer Bear River
Car Car River
Deer Car Bear

Mapper类

package cn.kgc.wordcount;

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

import java.io.IOException;

public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
    
    
    Text k = new Text();
    IntWritable v = new IntWritable(1);

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
    
    
        //获取一行数据,并转成字符串
        String line = value.toString();
        //对每一行数据进行处理
        String[] words = line.split("\\s+");
        //输出(word,1)
        for (String word : words) {
    
    
            k.set(word);
            context.write(k, v);
        }
    }
}

Reducer类

package cn.kgc.wordcount;

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

import java.io.IOException;

public class WordCountReducer extends Reducer<Text, IntWritable,Text,IntWritable> {
    
    
    int sum;
    IntWritable count = new IntWritable();
    @Override
    protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
    
    
        //定义初始变量
        sum=0;
        //遍历values
        for (IntWritable value : values) {
    
    
            sum+=value.get();
        }
        count.set(sum);
        //结果输出
        context.write(key,count);
    }
}

Driver类

package cn.kgc.wordcount;

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.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

import java.io.IOException;

public class WordCountDriver {
    
    
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
    
    
        //1.获取配置信息以及创建任务
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf);
        //2.指定Driver类程序jar所在路径
        job.setJarByClass(WordCountDriver.class);
        //3.指定Mapper和Reducer
        job.setMapperClass(WordCountMapper.class);
        job.setReducerClass(WordCountReducer.class);
        //4.指定Mapper端的输出类型(key和value)
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(IntWritable.class);
        //5.指定最终结果的输出类型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);
        //6.指定输入文件和输出文件的路径
        FileInputFormat.setInputPaths(job, new Path("file:///D:\\study\\codes\\hadoop\\HdfsClientDemo\\data\\hdfsDemo\\test.txt"));
        FileOutputFormat.setOutputPath(job, new Path("file:///D:\\study\\codes\\hadoop\\HdfsClientDemo\\data\\wordcount\\result"));
        //7.提交任务执行代码
        boolean result = job.waitForCompletion(true);
        System.exit(result ? 0 : 1);
    }
}

本地运行结果:
在这里插入图片描述


六、Hadoop序列化

序列化(Serialization)是指把结构化对象转化为字节流。
反序列化(Deserialization)是序列化的逆过程。把字节流转为结构化对象。
当要在进程间传递对象或持久化对象的时候,就需要序列化对象成字节流,反之当要将接收到或从磁盘读取的字节流转换为对象,就要进行反序列化。

Java 的序列化(Serializable)是一个重量级序列化框架一个对象被序列化后,会附带很多额外的信息(各种校验信息,header,继承体系…),不便于在网络中高效传输,所以,hadoop 自己开发了一套序列化机制( Writable),精简,高效。

常用的数据类型对应的 hadoop 数据序列化类型

Java类型 Hadoop Writable 类型
boolean BooleanWritable
byte ByteWritable
int IntWritable
float FloatWritable
long LongWritable
double DoubleWritable
string Text
map MapWritable
array ArrayWritable

自定义bean对象实现序列化接口 (Writable)
(1)必须实现Writable接口
(2)反序列化时,需要反射调用空参构造函数,所以必须有空参构造

public FlowBean() {
    
    
	super();
}

(3)重写序列化方法

@Override
public void write(DataOutput out) throws IOException {
    
    
out.writeLong(upFlow);
out.writeLong(downFlow);
out.writeLong(sumFlow);
}

(4)重写反序列化方法

@Override
public void readFields(DataInput in) throws IOException {
    
    
upFlow = in.readLong();
downFlow = in.readLong();
sumFlow = in.readLong();
}

(5)注意反序列化的顺序和序列化的顺序完全一致
(6)要想把结果显示在文件中,需要重写toString(),且用”\t”分开,方便后续用
(7)如果需要将自定义的bean放在key中传输,则还需要实现comparable接口,因为mapreduce框中的shuffle过程一定会对key进行排序

@Override
public int compareTo(FlowBean o) {
    
    
return this.sumFlow > o.getSumFlow() ? -1 : 1;
}

七、Partitioner & Combiner

7.1、Partitioner类

  • 用于在Map端对key进行分区
      ■默认使用的是HashPartitioner
          ▪获取key的哈希值
          ▪使用key的哈希值对Reduce任务数求模
      ■决定每条记录应该送到哪个Reducer处理
  • 自定义Partitioner
      ■继承抽象类Partitioner,重写getPartition方法
      ■job.setPartitionerClass(MyPartitioner.class)

原理图(来源Partitioner)
在这里插入图片描述
大致过程
Mapper端的输出结果通过context.write()写入OutPutCollector
OutPutCollector将结果写入环形缓冲区
环形缓冲区的溢出条件是达到其大小的80%,在溢出前,HashPartitioner根据键值对的键hash出一个哈希值,然后用这个哈希值对ReduceTasks个数取模,来决定这个键值对进入哪个分区(Partition)
所有MapTask结束后ReduceTask启动,并主动从所有的MapTask端拷贝属于该分区的数据

测试内容

1363157985066 	13726230503	00-FD-07-A4-72-B8:CMCC	120.196.100.82	i02.c.aliimg.com		24	27	2481	24681	200
1363157995052 	13826544101	5C-0E-8B-C7-F1-E0:CMCC	120.197.40.4			4	0	264	0	200
1363157991076 	13926435656	20-10-7A-28-CC-0A:CMCC	120.196.100.99			2	4	132	1512	200
1363154400022 	13926251106	5C-0E-8B-8B-B1-50:CMCC	120.197.40.4			4	0	240	0	200
1363157993044 	18211575961	94-71-AC-CD-E6-18:CMCC-EASY	120.196.100.99	iface.qiyi.com	视频网站	15	12	1527	2106	200
1363157995074 	84138413	5C-0E-8B-8C-E8-20:7DaysInn	120.197.40.4	122.72.52.12		20	16	4116	1432	200
1363157993055 	13560439658	C4-17-FE-BA-DE-D9:CMCC	120.196.100.99			18	15	1116	954	200
1363157995033 	15920133257	5C-0E-8B-C7-BA-20:CMCC	120.197.40.4	sug.so.360.cn	信息安全	20	20	3156	2936	200
1363157983019 	13719199419	68-A1-B7-03-07-B1:CMCC-EASY	120.196.100.82			4	0	240	0	200
1363157984041 	13660577991	5C-0E-8B-92-5C-20:CMCC-EASY	120.197.40.4	s19.cnzz.com	站点统计	24	9	6960	690	200
1363157973098 	15013685858	5C-0E-8B-C7-F7-90:CMCC	120.197.40.4	rank.ie.sogou.com	搜索引擎	28	27	3659	3538	200
1363157986029 	15989002119	E8-99-C4-4E-93-E0:CMCC-EASY	120.196.100.99	www.umeng.com	站点统计	3	3	1938	180	200
1363157992093 	13560439658	C4-17-FE-BA-DE-D9:CMCC	120.196.100.99			15	9	918	4938	200
1363157986041 	13480253104	5C-0E-8B-C7-FC-80:CMCC-EASY	120.197.40.4			3	3	180	180	200
1363157984040 	13602846565	5C-0E-8B-8B-B6-00:CMCC	120.197.40.4	2052.flash2-http.qq.com	综合门户	15	12	1938	2910	200
1363157995093 	13922314466	00-FD-07-A2-EC-BA:CMCC	120.196.100.82	img.qfc.cn		12	12	3008	3720	200
1363157982040 	13502468823	5C-0A-5B-6A-0B-D4:CMCC-EASY	120.196.100.99	y0.ifengimg.com	综合门户	57	102	7335	110349	200
1363157986072 	18320173382	84-25-DB-4F-10-1A:CMCC-EASY	120.196.100.99	input.shouji.sogou.com	搜索引擎	21	18	9531	2412	200
1363157990043 	13925057413	00-1F-64-E1-E6-9A:CMCC	120.196.100.55	t3.baidu.com	搜索引擎	69	63	11058	48243	200
1363157988072 	13760778710	00-FD-07-A4-7B-08:CMCC	120.196.100.82			2	2	120	120	200
1363157985066 	13560436666	00-FD-07-A4-72-B8:CMCC	120.196.100.82	i02.c.aliimg.com		24	27	2481	24681	200
1363157993055 	13560436666	C4-17-FE-BA-DE-D9:CMCC	120.196.100.99			18	15	1116	954	200

将数据放到HDFS上
在这里插入图片描述

输入格式

1368544993057 13568795243 C4-17-FE-BA-DE-D9:CMCC 120.196.100.99 18 15 1116 954 200
数据描述: 手机号码 上行流量 下行流量

目标输出格式

13568795243 1116 954 2070
手机号码 上行流量 下行流量 总流量

基本思路
Map 阶段
(1)读取文件中的每一行数据,按“\t”切分出字段。
(2)提取出手机号、上行流量、下行流量。
(3)以手机号为 key,bean 对象为 value 输出,即 context.write(手机号,Bean)。
Reduce 阶段:
(1)按照手机号累加求和算出上行流量和下行流量得到总流量。
(2)实现自定义的 bean 来封装流量信息,并将 bean 作为输出的 key 来传输。
(3)MapRedduce 程序在处理数据的过程中会对数据排序(map 输出的 kv 对传输到 reduce 之前,会排序),排序的依据是 map 输出的 key。

编写mapreduce程序

序列化 Bean 对象

package cn.kgc.flow;

import org.apache.hadoop.io.Writable;

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


public class FlowBean implements Writable {
    
    
    //定义相关属性
    private long upFlow;
    private long downFlow;
    private long sumFlow;

    public FlowBean() {
    
    
    }

    public long getUpFlow() {
    
    
        return upFlow;
    }

    public void setUpFlow(long upFlow) {
    
    
        this.upFlow = upFlow;
    }

    public long getDownFlow() {
    
    
        return downFlow;
    }

    public void setDownFlow(long downFlow) {
    
    
        this.downFlow = downFlow;
    }

    public long getSumFlow() {
    
    
        return sumFlow;
    }

    public void setSumFlow(long sumFlow) {
    
    
        this.sumFlow = sumFlow;
    }

    @Override
    public String toString() {
    
    
        return upFlow + "\t" + downFlow + "\t" + sumFlow;
    }

    //序列化方法
    public void write(DataOutput out) throws IOException {
    
    
        out.writeLong(upFlow);
        out.writeLong(downFlow);
        out.writeLong(sumFlow);

    }

    //反序列化方法
    public void readFields(DataInput in) throws IOException {
    
    
        this.upFlow = in.readLong();
        this.downFlow = in.readLong();
        this.sumFlow = in.readLong();
    }

    //set方法
    public void set(long upFlow, long downFlow) {
    
    
        this.upFlow = upFlow;
        this.downFlow = downFlow;
        this.sumFlow = upFlow+downFlow;
    }
}

Mapper类

package cn.kgc.flow;

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

import java.io.IOException;

public class FlowMapper extends Mapper<LongWritable, Text, Text, FlowBean> {
    
    
    Text k = new Text();
    FlowBean v = new FlowBean();

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
    
    
        //1368544993057 	13568795243	C4-17-FE-BA-DE-D9:CMCC	120.196.100.99		18	15	1116		954		200
        String[] split = value.toString().split("\t");
        String phone = split[1];
        long upFlow = Long.parseLong(split[split.length - 3]);
        long downFlow = Long.parseLong(split[split.length - 2]);
        k.set(phone);

        v.set(upFlow,downFlow);
        context.write(k,v);
    }
}

Reducer类

package cn.kgc.flow;

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

import java.io.IOException;


public class FlowReducer extends Reducer<Text, FlowBean, Text, FlowBean> {
    
    
    FlowBean v = new FlowBean();
    @Override
    protected void reduce(Text key, Iterable<FlowBean> values, Context context) throws IOException, InterruptedException {
    
    
        long sum_upFlow = 0;
        long sum_downFlow = 0;

        for (FlowBean flowBean : values) {
    
    
            sum_upFlow += flowBean.getUpFlow();
            sum_downFlow += flowBean.getDownFlow();
        }
        v.set(sum_upFlow,sum_downFlow);
        context.write(key,v);
    }
}

自定义Partitioner类

package cn.kgc.flow;

import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;

public class MyPartitioner extends Partitioner<Text, FlowBean> {
    
    
    @Override
    public int getPartition(Text text, FlowBean flowBean, int numPartitions) {
    
    
        //1.按照电话号码的开头取三位,进行分区
        String prenum = text.toString().substring(0, 3);
        //2.按照开头进行分区 135 136 137 138
        int partition = 4;
        if ("135".equals(prenum)) {
    
    
            partition = 0;
        } else if ("136".equals(prenum)) {
    
    
            partition = 1;
        } else if ("137".equals(prenum)) {
    
    
            partition = 2;
        } else if ("138".equals(prenum)) {
    
    
            partition = 3;
        }
        return partition;
    }
}

Driver类

package cn.kgc.flow;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
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;

import java.io.IOException;


public class FlowDriver {
    
    
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
    
    
        Configuration conf = new Configuration();
        conf.set("fs.defaultFS","hdfs://192.168.247.130:9000");

        Job job = Job.getInstance(conf);
        job.setJarByClass(FlowDriver.class);

        job.setMapperClass(FlowMapper.class);
        job.setReducerClass(FlowReducer.class);

        //设置自定义分区类
        job.setPartitionerClass(MyPartitioner.class);

        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(FlowBean.class);

        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(FlowBean.class);

        FileInputFormat.setInputPaths(job,new Path("/partition/phone_data.txt"));
        FileOutputFormat.setOutputPath(job,new Path("/partition/result"));

        //默认使用HashPartioner,分区数由ReduceTasks决定
        //设置ReduceTasks数量
        //如果使用自定义分区,定义了分区数,则ReduceTasks个数不能小于分区数
        //如果ReduceTasks个数大于分区数,那么会有Reduce Task接收不到数据,浪费资源
        job.setNumReduceTasks(5);
        boolean result = job.waitForCompletion(true);
        System.exit(result?0:1);
    }
}

执行后查看结果文件
在这里插入图片描述
手机号以135开头的分区

[root@single ~]# hdfs dfs -cat /partition/result/part-r-00000
13502468823	7335	110349	117684
13560436666	3597	25635	29232
13560439658	2034	5892	7926

手机号以136开头的分区

[root@single ~]# hdfs dfs -cat /partition/result/part-r-00001
13602846565	1938	2910	4848
13660577991	6960	690	7650

…此处省略
其余手机号的分区

[root@single ~]# hdfs dfs -cat /partition/result/part-r-00004
13480253104	180	180	360
13922314466	3008	3720	6728
13925057413	11058	48243	59301
13926251106	240	0	240
13926435656	132	1512	1644
15013685858	3659	3538	7197
15920133257	3156	2936	6092
15989002119	1938	180	2118
18211575961	1527	2106	3633
18320173382	9531	2412	11943
84138413	4116	1432	5548

7.2、Combiner类

  • Combiner相当于本地化的Reduce操作
      ■在shuffle之前进行本地聚合
      ■可有可无,用于性能优化
      ■输入和输出类型一致
  • Reducer可以被用作Combiner的条件
      ■符合交换律和结合律
  • 实现Combiner
      ■job.setCombinerClass(XXXReducer.class)

不使用Combiner
在这里插入图片描述
使用Combiner
在这里插入图片描述
使用Combiner后,需要传输到reducer的key变少了,若key的重复率越高,使用Combiner带来的优化更加明显!


八、InputFormat&OutputFormat

在这里插入图片描述


8.1、InputFormat

(1) 定义了如何将数据读入Mapper

  1. InputSplit[] getSplits
  2. RecordReader<K,V> getRecordReader

(2) 常用InputFormat接口实现类

  1. FileInputFormat(抽象类)
    1.1 TextInputFormat
    1.2 KeyValueTextInputFormat
  2. DBInputFormat

8.2、OutputFormat


九、join多种应用

十、总结

猜你喜欢

转载自blog.csdn.net/weixin_48482704/article/details/110846346
今日推荐