03 MapReduce & Yarn

1 MapReduce概述

1.1 MapReduce定义

分布式运算程序的编程框架

开发“基于Hadoop的数据分析应用”的核心框架

核心功能:将用户编写的业务逻辑代码+自带默认组件 —>整合为分布式运算程序,并发运行在Hadoop集群上。

1.2 MapReduce优缺点

1.2.1 优点

● 易于编程 — 实现接口,就可完成一个分布式程序

● 良好的扩展性 — 增加机器扩展计算能力

● 高容错性 — 机器挂了,任务转移

● PB级海量数据离线处理 — 可实现上千台服务器集群并发工作

1.2.2 缺点

● 不擅长实时计算

● 不擅长流式计算 — 输入数据集时静态的

● 不擅长DAG(有向图)计算 — 输出结果写入磁盘,大量IO,导致性能低下

1.3 MapReduce核心思想

MapTask阶段:并发实例,完全并行运行,互不相干

ReduceTask阶段:并发实例互不相干,依赖MapTask的输出

MapReduce模型包含一个Map阶段、一个Reduce阶段。用户逻辑复杂,多个MapReduce程序,串行运行。

1.4 MapReduce进程

一个完整的MapReduce程序有三类实例进程

MrAppMaster:负责整个程序的过程调度状态协调

MapTask:负责整个数据处理流程(Map阶段)。

ReduceTask: 负责整个数据处理流程(Reduce阶段)

1.5 官方WordCount源码

采用反编译工具反编译源码

发现WordCount案例有Map类、Reduce类和驱动类

数据的类型是Hadoop自身封装的序列化类型

1.6 WordCount案例

▶ 需求:在给定的文本文件中统计输出每一个单词出现的总次数

需求分析:

MapReduce编程规范,分别编写Mapper,Reducer,Driver。

【案例1】WCMapper继承Mapper

类的介绍

① Mapper类 — 当执行MapTask时回调用此类

Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>
KEYIN 读取数据时的偏移量类型
VALUEIN 读取一行一行的内容的类型
KEYOUT 写出时的key的类型(单词的类型)
VALUEOUT 写出时的value的类型(单词数量的类型)

方的介绍

② map() — mapper类会调用此方法(此方法在被循环调用-每调用一些传入一行数据)

map(LongWritable key, Text value, Context context)
key 读取数据时的偏移量
value 读取的一行一行的数据
context 上下文(在这用来将Key,value写出去)
public class WCMapper extends Mapper<LongWritable, Text,Text, LongWritable> {
    //封装Key
    private  Text outKey = new Text();
    //封装Value
    private LongWritable outValue = new LongWritable();

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        //super.map(key, value, context); 一定不要调用父类中的map方法

        //1.将数据切割
        //1.1 将Test转成String
        String line = value.toString();
        //1.2 切割数据
        String[] words = line.split(" ");

        for (String word : words) {
            //2.封装k,v
            outKey.set(word);//给key赋值
            outValue.set(1);//给value赋值
            //3.将k,v写出去
            context.write(outKey,outValue);
        }
    }
}

【案例2】WCReducer继承Reducer

类的介绍

① WCReducer — ReduceTask会调用此类

Reducer<KEYIN, VALUEIN, KEYOUT, VALUEOUT>
KEYIN Reducer读取key的类型(Mapper写出的key的类型--在这是单词)
VALUEIN Reducer读取value的类型(Mapper写出的value的类型--在这是单词的数量)
KEYOUT Reducer写出的key的类型(在这是单词的类型)
VALUEOUT Reducer写出的value的类型(在这是单词数量的类型)

方的介绍

reduce() — 该方法会被Reducer类所调用(该方法也是被循环调用每调用一次传入一组数据)

reduce(Text key, Iterable<LongWritable> values, Context context)
key 读取的key 在这就是单词
value 读取的所有的value 在这就是单词的数量
context 上下文(在这用来将key,value写出去)
public class WCReducer extends Reducer<Text, LongWritable,Text, LongWritable> {
    //封装的value
    private LongWritable outValue = new LongWritable();

    @Override
    protected void reduce(Text key, Iterable<LongWritable> values, Context context) throws IOException, InterruptedException {
        long sum = 0;//所有value的和
        //1.遍历所有的value
        for (LongWritable value : values) {
            //将LongWritable转成long类型
            long l = value.get();
            //2.将所有value累加
            sum += l;
        }
        //3.封装k,v (这次的k不用封装哟!)
        outValue.set(sum);//给value赋值
        //4.将k,v写出去
        context.write(key,outValue);
    }
}

【案例3】本地运行 (本地->本地)

 方的介绍

waitForCompletion(boolean verbose) 
参数 是否打印用户进度
返回值 如果为true表示执行成功
public class WCDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        //1.创建Job实例
        Configuration conf = new Configuration();  //可以往里面放配置参数,但是我们现在先不放
        Job job = Job.getInstance(conf);

        //2.给Job赋值
        //2.1设置Jar加载路径(如果是本地模式可以不设置)
        job.setJarByClass(WCDriver.class);
        //2.2设置Mapper和Reducer类
        job.setMapperClass(WCMapper.class);
        job.setReducerClass(WCReducer.class);
        //2.3设置Mapper输出的类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(LongWritable.class);
        //2.4设置最终输出的k,v类型(在这是Reducer)
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(LongWritable.class);
        //2.5设置输入和输出路径
        FileInputFormat.setInputPaths(job,new Path("D:\\io\\input"));
        //注意:输出路径一定不能存在否则报错
        FileOutputFormat.setOutputPath(job,new Path("D:\\io\\output"));

        //3.提交Job
        /*
            参数 :是否打印用户进度
            返回值 :如果为true表示执行成功
         */
        boolean b = job.waitForCompletion(true);

    }
}

运行结果:

【案例4】在集群上跑Job (HDFS->HDFS) 

① 向mian()方法传入输入、输出路径

② 打包jar

③ 将jar包上传到任意节点上(这里上传到hadoop102的家目录)

④ 执行命令 — hadoop jar 包名 全类名 HDFS的输入路径 HDFS的输出路径

public class WCDriver2 {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        //1.创建Job实例
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf);

        //2.给Job赋值
        //2.1设置Jar加载路径(如果是本地模式可以不设置)
        job.setJarByClass(WCDriver2.class);
        //2.2设置Mapper和Reducer类
        job.setMapperClass(WCMapper.class);
        job.setReducerClass(WCReducer.class);
        //2.3设置Mapper输出的类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(LongWritable.class);
        //2.4设置最终输出的k,v类型(在这是Reducer)
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(LongWritable.class);
        //2.5设置输入和输出路径
        //此时的路径不能写死,通过参数闯入
        FileInputFormat.setInputPaths(job,new Path(args[0]));
        //注意:输出路径一定不能存在否则报错
        FileOutputFormat.setOutputPath(job,new Path(args[1]));

        //3.提交Job
        /*
            参数 :是否打印用户进度
            返回值 :如果为true表示执行成功
         */
        boolean b = job.waitForCompletion(true);

    }
}

运行结果:

【案例5】从本地向集群提交job (本地 —> HDFS)  [一般调试的时候使用此方法]

① 配置 (下列代码可见)

② 打包,打包后复制jar包的绝对路径

③ 注释掉 job.setJarByClass(WCDriver3.class)

④ 添加 job.setJar(jar包的路径) 

⑤ 配置

VM Options : -DHADOOP_USER_NAME=atguigu

ProgramArguments :hdfs://hadoop102:8020/input hdfs://hadoop102:8020/output

public class WCDriver3 {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        //1.创建Job实例
        Configuration conf = new Configuration();
        //1.1 进行配置
        //设置在集群运行的相关参数-设置HDFS,NAMENODE的地址
        conf.set("fs.defaultFS", "hdfs://hadoop102:8020");
        //指定MR运行在Yarn上
        conf.set("mapreduce.framework.name","yarn");
        //指定MR可以在远程集群运行
        conf.set("mapreduce.app-submission.cross-platform","true");
        //指定yarn resourcemanager的位置
        conf.set("yarn.resourcemanager.hostname", "hadoop103");
        //1.2 获取配置后的对象
        Job job = Job.getInstance(conf);

        //2.给Job赋值
        //2.1设置Jar加载路径(如果是本地模式可以不设置)
        //job.setJarByClass(WCDriver3.class);
        job.setJar("D:\\develop\\1025project\\MRDemo\\target\\MRDemo-1.0-SNAPSHOT.jar");

        //2.2设置Mapper和Reducer类
        job.setMapperClass(WCMapper.class);
        job.setReducerClass(WCReducer.class);
        //2.3设置Mapper输出的类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(LongWritable.class);
        //2.4设置最终输出的k,v类型(在这是Reducer)
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(LongWritable.class);
        //2.5设置输入和输出路径
        FileInputFormat.setInputPaths(job,new Path(args[0]));
        //注意:输出路径一定不能存在否则报错
        FileOutputFormat.setOutputPath(job,new Path(args[1]));

        //3.提交Job
        boolean b = job.waitForCompletion(true);

    }
}

 

2 Hadoop序列化

2.1 序列化概述

2.1.1 什么时序列化

序列化:内存中的对象 —> 磁盘

反序列化:磁盘 —> 内存中的对象

2.1.3 为什么不用Java的序列化

Java序列化,会附带很多额外的信息,不便于在网络中高效传输。

Hadoop自己开发了一套序列化机制(Writable)。

Hadoop序列化特点:

   紧凑:高效使用存储空间

   快速:读写数据的额外开销小

   可扩展:随着通信协议的升级而可升级

   互操作:支持多语言交互

2.2 自定义bean对象实现序列化接口(Writable)

【案例】自定义的类的对象实现序列化  (主要看 1. 和 2. )

① 自定义的类实现Writable接口

② 重写write和readFields方法

注意:反序列化时的顺序和序列化时的顺序应该保持一致

public class FlowBean implements Writable {

    private long upFlow;
    private long downFlow;
    private long sumFlow;

    public FlowBean(){

    }

    //1.序列化时调用此方法
    public void write(DataOutput out) throws IOException {
        out.writeLong(upFlow);
        out.writeLong(downFlow);
        out.writeLong(sumFlow);
    }

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


    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;
    }
}

2.3 序列化实例实操

需求:统计每一个手机号耗费的总上行流量、下行流量、总流量

▶ 编写MapReduce程序

【案例1】FlowBean

public class FlowBean implements Writable {
    private long upFlow;
    private long downFlow;
    private long sumFlow;

    public FlowBean(){

    }

    /*
        序列化时调用此方法
     */
    public void write(DataOutput out) throws IOException {
        out.writeLong(upFlow);
        out.writeLong(downFlow);
        out.writeLong(sumFlow);
    }

    /*
        反序列化时调用此方法
        注意:反序列化时的顺序和序列化时的顺序应该保持一致
     */
    public void readFields(DataInput in) throws IOException {
        upFlow = in.readLong();
        downFlow = in.readLong();
        sumFlow = in.readLong();
    }

    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 + " " + downFlow + " " + sumFlow;
    }
}

【案例2】FlowMapper

/*
    Mapper类 :当执行MapTask时会调用此类

    Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>  :
        四个泛型分两组:
            第一组
                KEYIN :读取数据时的偏移量的类型
                VALUEIN :读取的一行一行的内容的类型
            第二组
                KEYOUT :写出时的key的类型(在这是手机号的类型)
                VALUEOUT :写出时的value的类型(在这是FlowBean的类型)
 */
public class FlowMapper extends Mapper<LongWritable, Text,Text,FlowBean> {
    //写出的key
    private Text outKey = new Text();
    //写出的value
    private FlowBean outValue = new FlowBean();
    /**
     *  mapper类会调用此方法(此方法在被循环调用-每调用一些传入一行数据)
     * @param key 读取数据时的偏移量
     * @param value 读取的一行一行的数据
     * @param context 上下文(在这用来将Key,value写出去)
     */
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        //1.将数据切割
        //1.1将Text转成String
        String line = value.toString();
        //1.2切割数据
        String[] phoneInfo = line.split("\t");
        //2.封装k,v
        outKey.set(phoneInfo[1]);//给key赋值
        //给value赋值
        outValue.setUpFlow(Long.parseLong(phoneInfo[phoneInfo.length - 3]));
        outValue.setDownFlow(Long.parseLong(phoneInfo[phoneInfo.length - 2]));
        outValue.setSumFlow(outValue.getUpFlow() + outValue.getDownFlow());
        //3.将k,v写出
        context.write(outKey,outValue);
    }
}

【案例3】FlowReducer

/*

ReduceTask会调用此类

    Reducer<KEYIN, VALUEIN, KEYOUT, VALUEOUT> :
        泛型分两组
            第一组:
                KEYIN :Reducer读取key的类型(Mapper写出的key的类型--在这是手机号)
                VALUEIN :Reducer读取value的类型(Mapper写出的value的类型--在这是FlowBean)
            第二组:
                KEYOUT :Reducer写出的key的类型(在这是手机号的类型)
                VALUEOUT :Reducer写出的value的类型(在这是FlowBean的类型)
 */
public class FlowReducer extends Reducer<Text,FlowBean,Text,FlowBean> {
    //封装value
    private FlowBean outValue = new FlowBean();
    /*
    /**
     * 该方法会被Reducer类所调用(该方法也是被循环调用每调用一次传入一组数据)
     * @param key 读取的key 在这就是单词
     * @param values 读取的所有的value 在这就是单词的数量
     * @param context 上下文(在这用来将key,value写出去)
     * @throws IOException
     * @throws InterruptedException
     *      key                     value
     * 1532111111     --- >   100 120 220
       1532111111     --- >   80  20  100
     */
    @Override
    protected void reduce(Text key, Iterable<FlowBean> values, Context context) throws IOException, InterruptedException {
        long sumUpFlow = 0; //总上行流量
        long sumDowFlow = 0; //总下行流量
        //1.遍历所有的value
        for (FlowBean value : values) {
            //2.将上行和下行流量进行累加
            sumUpFlow += value.getUpFlow();
            sumDowFlow += value.getDownFlow();
        }
        //3.封装k,v
        outValue.setUpFlow(sumUpFlow);
        outValue.setDownFlow(sumDowFlow);
        outValue.setSumFlow(sumDowFlow+sumUpFlow);
        //4.写出K,V
        context.write(key,outValue);
    }
}

【案例4】FlowDriver

public class FlowDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        //1.创建Job实例
        Job job = Job.getInstance(new Configuration());

        //2.给Job赋值
        //2.1设置Jar加载路径(如果是本地模式可以不设置)
        //2.2设置Mapper和Reducer类
        job.setMapperClass(FlowMapper.class);
        job.setReducerClass(FlowReducer.class);
        //2.3设置Mapper输出的类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(FlowBean.class);
        //2.4设置最终输出的k,v类型(在这是Reducer)
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(FlowBean.class);
        //2.5设置输入和输出路径
        FileInputFormat.setInputPaths(job,new Path("D:\\io\\input"));
        //注意:输出路径一定不能存在否则报错
        FileOutputFormat.setOutputPath(job,new Path("D:\\io\\output"));
        //3.提交Job
        /*
            参数 :是否打印用户进度
            返回值 :如果为true表示执行成功
         */
        job.waitForCompletion(true);
    }
}

运行结果:


 

3 MapReduce框架原理

3.1 InputFormat数据输入

3.1.1 切片与MapTask并行度决定机制

3.1.2 Job提交流程源码和切片源码详解

3.1.3 FileInputFormat切片机制

切片机制

参数配置

3.1.4 CombineTextInputFormat切片机制

默认的TextInputFormat切片机制,不管文件多小,都会是一个单独的切片。

1) 应用场景:

CombineTextInputFormat 用于小文件过多的场景,可将多个小文件从逻辑上整成一个切片,然后交给一个MapTask处理。

2)虚拟存储切片最大值设置

CombineTextInputFormat.setMaxInputSplitSize(job, 4194304);  // 4m

3)切片机制

生成切片过程包括:虚拟存储过程和切片过程二部分。

 

3.1.5 CombineTextInputFormat案例实操

3.6 TextInputFormat的KV

● TextInputFormat是默认的FileInputFormat实现类。按行读取每条记录。

是存储该行在整个文件中的起始字节偏移量,LongWritable类型

是这行的内容,不包括任何终止符,Text类型

 

3.2 MapReduce工作流程

 

3.3 Shuffle机制

3.3.1 Shuffle机制

3.3.2 Partition分区

 

posted @ 2024-05-13 23:11  白森  阅读(6)  评论(0编辑  收藏  举报