大数据技术之Hadoop(MapReduce)概述、序列化


1 MapReduce 概述

1.1 MapReduce 定义

MapReduce是一个分布式运算程序的编程框架,是用户开发“基于Hadoop的数据分析应用”的核心框架。
MapReduce核心功能是将用户编写的业务逻辑代码自带默认组件整合成一个完整的分布式运算程序 ,并发运行在一个 Hadoop集群上。

1.2 MapReduce 优缺点

MapReduce优点

(1)MapReduce易于编程它简单的实现一些接口,就可以完成一个分布式程序,这个分布式程序可以分布到大量廉价的 PC机器上运行。也就是说你写一个分布式程序,跟写一个简单的串行程序是一模一样的。就是因为这个特点使得 MapReduce编程变得非常流行。
(2)良好的扩展性:当你的计算资源不能得到满足的时候,你可以通过简单的增加机器来扩展它的计算能力。
(3)高容错性:MapReduce设计的初衷就是使程序能够部署在廉价的 PC机器上,这就要求它具有很高的容错性。比如其中一台机器挂了,它可以把上面的计算任务转移到另外一个节点上运行,不至于这个任务运行失败, 而且这个过程不需要人工参与,而完全是由 Hadoop内部完成的。
(4)适合 PB级以上海量数据的离线处理:可以实现上千台服务器集群并发工作,提供数据处理能力。

MapReduce缺点

(1)不擅长实时计算:MapReduce 无法像MySQL 一样,在毫秒或者秒级内返回结果。
(2)不擅长流式计算: 流式计算的输入数据是动态的,而MapReduce 的输入数据集是静态的,不能动态变化。这是因为MapReduce 自身的设计特点决定了数据源必须是静态的。
(3)不擅长DAG(有向无环图)计算:多个应用程序存在依赖关系,后一个应用程序的输入为前一个的输出。在这种情况下,MapReduce 并不是不能做,而是使用后,每个MapReduce 作业的输出结果都会写入到磁盘,会造成大量的磁盘IO,导致性能非常的低下。

1.3 MapReduce 核心思想

在这里插入图片描述

(1)分布式的运算程序往往需要分成至少2 个阶段。
(2)第一个阶段的MapTask 并发实例,完全并行运行,互不相干。
(3)第二个阶段的ReduceTask 并发实例互不相干,但是他们的数据依赖于上一个阶段的所有MapTask 并发实例的输出。
(4)MapReduce 编程模型只能包含一个Map 阶段和一个Reduce 阶段,如果用户的业务逻辑非常复杂,那就只能多个MapReduce 程序,串行运行。
总结:分析WordCount 数据流走向深入理解MapReduce 核心思想。

1.4 MapReduce 进程

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

(1)MrAppMaster:负责整个程序的过程调度及状态协调。
(2)MapTask:负责Map 阶段的整个数据处理流程。
(3)ReduceTask:负责Reduce 阶段的整个数据处理流程。

1.5 官方WordCount 源码

采用反编译工具反编译源码,发现WordCount 案例有Map 类、Reduce 类和驱动类。且数据的类型是Hadoop 自身封装的序列化类型。

1.6 常用数据序列化类型

Java类型 Hadoop Writable 类型
Boolean BooleanWritable
Byte ByteWritable
Int IntWritable
Float FloatWritable
Long LongWritable
Double DoubleWritable
String Text
Map MapWritable
Array ArrayWritable
Null NullWritable

1.7 MapReduce 编程规范

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

Mapper阶段
(1)用户自定义的Mapper要继承自己的父类
(2)Mapper的输入数据是KV对的形式(KV的类型可自定义)
(3)Mapper中的业务逻辑写在map()方法中
(4)Mapper的输出数据是KV对的形式(KV的类型可自定义)
(5)map()方法(MapTask进程)对每一个<K,V>调用一次

Reducer阶段
(1)用户自定义的Reducer要继承自己的父类
(2)Reducer的输入数据类型对应Mapper的输出数据类型,也是KV
(3)Reducer的业务逻辑写在reduce()方法中
(4)ReduceTask进程对每一组相同k的<k,v>组调用一次reduce()方法

Driver阶段
相当于YARN集群的客户端,用于提交我们整个程序到YARN集群,提交的是
封装了MapReduce程序相关运行参数的job对象。

1.8 WordCount 案例实操

1.8.1 本地测试

(1)需求
在给定的文本文件中统计输出每一个单词出现的总次数。原文件如下:
在这里插入图片描述
(2)需求分析
按照MapReduce 编程规范,分别编写Mapper,Reducer,Driver。

(3)环境准备
(a)创建maven 工程,MapReduceDemo
(b)在pom.xml 文件中添加如下依赖

<dependencies>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-client</artifactId>
            <version>3.1.3</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.30</version>
        </dependency>
</dependencies>

(c)在项目的src/main/resources 目录下,新建一个文件,命名为“log4j.properties”,在文件中填入

log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] -%m%n
log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.File=target/spring.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] -%m%n

(d)创建包名:com.Tom.mapreduce.wordcount

(4)编写程序
(a)编写 Mapper类

package com.Tom.mapreduce.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;

/**
 * KEYIN, map阶段输入的key的类型:LongWritable
 * VALUEIN, map阶段的value类型:Text
 * KEYOUT, map阶段输出的Key类型:Text
 * VALUEOUT, map阶段输出的value类型:IntWritable
 */

public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
    
    
    private Text outK = new Text();
    private IntWritable outV = new IntWritable(1);

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
    
    
        // 1 获取1行
        // hello hello
        String line = value.toString();

        // 2 切割
        // hello
        // hello
        String[] words = line.split(" ");

        // 3 循环写出
        for (String word : words) {
    
    
            // 封装outK
            outK.set(word);

            // 写出
            context.write(outK, outV);
        }
    }
}

(b)编写Reducer类

package com.Tom.mapreduce.wordcount;

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

import java.io.IOException;

/**
 * KEYIN, reduce阶段输入的key的类型:Text
 * VALUEIN, reduce阶段的value类型:IntWritable
 * KEYOUT, reduce阶段输出的Key类型:Text
 * VALUEOUT, reduce阶段输出的value类型:IntWritable
 */

public class WordCountReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
    
    
    private IntWritable outV = new IntWritable();

    @Override
    protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
    
    
        int sum = 0;
        // hello, (1, 1)
        // 累加
        for (IntWritable value : values) {
    
    
            sum += value.get();
        }

        outV.set(sum);

        // 写出
        context.write(key, outV);
    }
}

(b)编写Dirver类

package com.Tom.mapreduce.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 获取job
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf);

        // 2 设置jar包路径
        job.setJarByClass(WordCountDriver.class);

        // 3 关联mapper和reducer
        job.setMapperClass(WordCountMapper.class);
        job.setReducerClass(WordCountReducer.class);

        // 4 设置map输出的kv类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(IntWritable.class);

        // 5 设置最终输出的kv类型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);

        // 6 设置输入路径和输出路径
        FileInputFormat.setInputPaths(job, new Path("E:\\input\\inputword"));
        FileOutputFormat.setOutputPath(job, new Path("E:\\hadoop\\output1"));

        // 7 提交job
        boolean result = job.waitForCompletion(true);

        System.exit(result ? 0 : 1);
    }
}

(5)本地测试
(a)需要首先配置好 HADOOP_HOME变量以及 Windows运行依赖
(b)在 IDEA/Eclipse上运行程序,结果如下:
在这里插入图片描述
在这里插入图片描述

1.8.2 提交到集群测试

(1)用 maven打 jar包需要添加的打包插件依赖

<build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.6.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

注意:如果工程上显示红叉,在项目上右键 ->maven->Reimport刷新即可。

(2)与本地测试相比,集群测试要修改Dirver类中的输入输出路径,修改之后将程序打成 jar包。

// 6 设置输入路径和输出路径
        FileInputFormat.setInputPaths(job, new Path(args[0]));
        FileOutputFormat.setOutputPath(job, new Path(args[1]));

在这里插入图片描述
(3)修改不带依赖的 jar包名称 为 wc.jar,并拷贝该 jar包到 Hadoop集群 的/opt/module/hadoop-3.1.3路径 。

(4)启动Hadoop 集群

[Tom@hadoop102 hadoop-3.1.3]$ sbin/start-dfs.sh
[Tom@hadoop103 hadoop-3.1.3]$ sbin/start-yarn.sh

(4)执行WordCount 程序

[Tom@hadoop102 hadoop-3.1.3]$ hadoop jar wc.jar com.Tom.mapreduce.wordcount2.WordCountDriver /input /output

在这里插入图片描述

2 Hadoop 序列化

2.1 序列化概述

什么是序列化?

序列化就是把内存中的对象,转换成字节序列 (或其他数据传输协议)以便于存储到磁盘(持久化)和网络传输。
反序列化就是将收到字节序列(或其他数据传输协议)或者是磁盘的持久化数据,转换成内存中的对象。

为什么要序列化?

一般来说,“活的”对象只生存在内存里,关机断电就没有了。而且“活的”对象只由本地的进程使用,不能被发送到网络上的另外一台计算机。 然而序列化可以存储“活的”对象,可以将“活的”对象发送到远程计算机。

为什么不用 Java的序列化?

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

Hadoop序列化特点:

(1)紧凑 高效使用存储空间。
(2)快速 读写数据的额外开销小。
(3)互操作 支持多语言的交互。

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

在企业开发中往往常用的基本序列化类型不能满足所有需求,比如在 Hadoop框架内部传递一个 bean对象, 那么该对象就需要实现序列化接口。
具体实现 bean对象序列化步骤如下 7步 :
(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 {
    
    
        this.upFlow = in.readLong();
        this.downFlow = in.readLong();
        this.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;
}

2.3 序列化案例实操

需求

统计每一个手机号耗费的总上行流量、总下行流量、总流量
(1)输入数据

(2)输入数据格式
在这里插入图片描述
(3)期望输出数据格式
在这里插入图片描述
需求分析
在这里插入图片描述
编写MapReduce 程序

(1)编写流量统计的Bean 对象

package com.Tom.mapreduce.writable;

import org.apache.hadoop.io.Writable;

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

/**
 * 1. 定义类实现writable接口
 * 2. 重写序列化和反序列化方法
 * 3. 重写空参构造
 * 4. toString方法
 */
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;
    }

    public void setSumFlow() {
    
    
        this.sumFlow = this.upFlow + this.downFlow;
    }

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

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

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

(2)编写 Mapper类

package com.Tom.mapreduce.writable;

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> {
    
    

    private Text outK = new Text();
    private FlowBean outV = new FlowBean();

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
    
    

        // 1 获取一行
        // 1    13736230513 192.196.100.1   www.atguigu.com 2481    24681   200
        String line = value.toString();

        // 2 切割
        // 1,13736230513,192.196.100.1,www.atguigu.com,2481,24681,200
        // 2    138846544121 192.196.100.2      264 0   200
        String[] split = line.split("\t");

        // 3 抓取想要的数据
        // 手机号:13736230513
        // 上行流量和下行流量:2481,24681
        String phone = split[1];
        String up = split[split.length - 3];
        String down = split[split.length - 2];

        // 4 封装
        outK.set(phone);
        outV.setUpFlow(Long.parseLong(up));
        outV.setDownFlow(Long.parseLong(down));
        outV.setSumFlow();

        // 5 写出
        context.write(outK, outV);
    }
}

(3)编写 Reducer类

package com.Tom.mapreduce.writable;

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> {
    
    
    private FlowBean outV = new FlowBean();

    @Override
    protected void reduce(Text key, Iterable<FlowBean> values, Context context) throws IOException, InterruptedException {
    
    

        // 1 遍历集合累加值
        long totalUp = 0;
        long totalDown = 0;

        for (FlowBean value : values) {
    
    
            totalUp += value.getUpFlow();
            totalDown += value.getDownFlow();
        }

        // 2 封装outK, outV
        outV.setUpFlow(totalUp);
        outV.setDownFlow(totalDown);
        outV.setSumFlow();

        // 3 写出
        context.write(key, outV);
    }
}

(4)编写 Driver驱动类

package com.Tom.mapreduce.writable;

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 FlowDirver {
    
    
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
    
    

        // 1 获取job
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf);

        // 2 设置jar
        job.setJarByClass(FlowDirver.class);

        // 3 关联Mapper和Reducer
        job.setMapperClass(FlowMapper.class);
        job.setReducerClass(FlowReducer.class);

        // 4 设置Mapper输出的key和value类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(FlowBean.class);

        // 5 设置最终数据输出的key和value类型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(FlowBean.class);

        // 6 设置数据的输入路径和输出路径
        FileInputFormat.setInputPaths(job, new Path("E:\\input\\inputflow"));
        FileOutputFormat.setOutputPath(job,new Path("E:\\hadoop\\output2"));

        // 7 提交job
        job.waitForCompletion(true);
    }
}

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


参考:
https://www.bilibili.com/video/BV1Qp4y1n7ENspm_id_from=333.788.b_636f6d6d656e74.7

猜你喜欢

转载自blog.csdn.net/huxili2020/article/details/117986794