Hadoop_MapReduce实践 (二) => (核心架构、序列化、Inputformat/切片、Shuffile/分区/排序、outputformat、join、ETL、压缩)

目录

Hadoop_HDFS、Hadoop_MapReduce、Hadoop_Yarn 实践 (二)

一、Hadoop_HDFS

二、Hadoop_MapReduce

1、MapReduce概述

1.1、MapReduce定义

​ MapReduce是一个分布式运算程序的编程框架,是用户开发基于Hadoop的数据分享应用的核心框架。

​ MapReduce核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序并发运行在一个Hadoop集群上。

1.2、优缺点

优点:

  • 1、易于编程。用户只关系业务逻辑,实现框架的接口
  • 2、良好的扩展行:可以动态增加服务器,解决计算资源不够的问题。
  • 3、高容错性。任何一台down机,可以将任务转移到其他node。
  • 4、适合海量数据的计算(TB/PB)几千台服务器共同计算。

缺点:

  • 1、不擅长实时计算。一般都是分钟小时级别的或是运行几天的任务都有可能。(MySQL可以毫秒级计算)
  • 2、不擅长流式计算。(Sparkstreaming flink 擅长)
  • 3、不擅长DAG有向无环图计算。(Spark 擅长)
1.3、核心编程思想

在这里插入图片描述

1.4、MapReduce进程

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

  • 1、MrAppMaster:负责整个程序的过程调度及状态协调。
  • 2、MapTask:负责Map阶段的整个数据处理流程。
  • 3、ReduceTask:负责Reduce节点的整个数据处理流程
1.5、官方word count源码
# Hadoop提供的一些eg在如下默认的目录中
cd $HADOOP_HOME/share/hadoop/mapreduce

JD-GUI 是一款开源的反编译软件,通过它我们可以比较方便的进行jar包的反编译。可以用来参考相关的jar

Hadoop Writable序列化类型

在这里插入图片描述

1.6、统计文件中单词出现的重复个数(World count)实操

在这里插入图片描述

创建一个maven project (MapReduceDemo)

1.6.1、配置依赖 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.leotest</groupId>
    <artifactId>MapReduceDemo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.30</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-client</artifactId>
            <version>3.3.4</version>
        </dependency>
    </dependencies>

    <build>
        <!-- 指定最后构建打包成功的压缩包的名字 -->
        <finalName>helloworld-maven-java</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <!-- 指示maven用什么版本的jdk编译 -->
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>

            <!--将pom中所有的依赖全部打包进一个jar包中-->
<!--            <plugin>-->
<!--                <groupId>org.apache.maven.plugins</groupId>-->
<!--                <artifactId>maven-assembly-plugin</artifactId>-->
<!--                <version>3.3.0</version>-->
<!--                <configuration>-->
<!--                    <descriptorRefs>-->
<!--                        <descriptorRef>jar-with-dependencies</descriptorRef>-->
<!--                    </descriptorRefs>-->
<!--                </configuration>-->
<!--                <executions>-->
<!--                    <execution>-->
<!--                        &lt;!&ndash; 绑定到package生命周期 &ndash;&gt;-->
<!--                        <phase>package</phase>-->
<!--                        <goals>-->
<!--                            &lt;!&ndash; 只运行一次 &ndash;&gt;-->
<!--                            <goal>single</goal>-->
<!--                        </goals>-->
<!--                    </execution>-->
<!--                </executions>-->
<!--            </plugin>-->
        </plugins>
    </build>

</project>

配置日志格式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.RollingFileAppender
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
1.6.2、创建Mapper、Reducer、Driver

创建包名com.leojiang.mapreduce.wordcount

1)创建类:WordCountMapper
package com.leojiang.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, Mapper.Context context) throws IOException, InterruptedException {

        // 1、获取一行
        String line = value.toString();
        // 去除首尾空格
        String tline = line.trim();
        // 2、对这行数据进行切割,分割一个空格
//        String[] words = tline.split(" ");
        // s表示匹配任何空白字符,+表示匹配一次或多
        String[] words = tline.split("\\s+");

        // 3、循环写出
        for (String word : words) {
            outK.set(word);
            context.write(outK, outV);
        }
    }
}

2)创建类:WordCountReducer
package com.leojiang.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;
        // 累加
        for (IntWritable value : values) {
            sum += value.get();
        }

        outV.set(sum);

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

3)创建类:WordCountDriver
package com.leojiang.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);

        // 3 设置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 设置window本地,输入路径和输出路径
        FileInputFormat.setInputPaths(job, new Path("C:\\Leojiang\\leojiangDocument\\Hadoop\\tmp-testFile\\input"));
        FileOutputFormat.setOutputPath(job, new Path("C:\\Leojiang\\leojiangDocument\\Hadoop\\tmp-testFile\\output1"));

        // 7 提交job
        boolean result = job.waitForCompletion(true);
        System.exit(result ? 0 : 1);

    }
}

1.6.3、创建测试文件hello.txt,进行win本地测试

C:\\Leojiang\\leojiangDocument\\Hadoop\\tmp-testFile\\input (本文的测试文件存放路径)

win本地创建测试文件:hello.txt

leo test aa  
bb cc dd aa
 leo jiang test aa bb  cc
test1 test2 test
hadoop version
hadoop hadoop2 version

测试:idea run WordCountDriver将获得如下文件:
在这里插入图片描述

如下为我们所需的最终结果:
在这里插入图片描述

1.6.4、修改代码动态输入,linux上运行(imput和opt目录)

1)因为我们的代码最终需要运行在Linux服务器上,所以需要修改WordCountDriver中的文件路径即可:

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

2)然后打成jar包上传到Linux服务器

在这里插入图片描述

3)使用我们编写的方法即可:

hadoop jar helloworld-maven-java.jar com.leojiang.mapreduce.wordcount2.WordCountDriver /input /output
  • helloworld-maven-java.jar 我们生产的jar包名称。
  • com.leojiang.mapreduce.wordcount2.WordCountDriver必须使用全列名,可以右键WordCountDriver 类拷贝。(参考如下截图)
  • /input hadoop输入路径,需要count文件的路径
  • /output hadoop输出路径

在这里插入图片描述

2、序列化

2.1、简介

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

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

3)为什么不用 Java 的序列化

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

​ java序列化比较笨重,hadoop自己开发了一套序列化机制Writable,比较轻便,好传输

4)Hadoop 序列化特点(轻量、高效):

  • 紧凑 :高效使用存储空间。
  • 快速:读写数据的额外开销小。
  • 互操作:支持多语言的交互
2.2、序列化案例实操
2.2.1、需求

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

1)输入数据

cat phone_data.txt
1       13736230513     10.196.100.1    www.leotest.com 2481    24681   200
2       13846544121     10.196.100.2                    264     0       200
3       13956435636     10.196.100.3                    132     1512    200
4       13966251146     10.168.100.1                    240     0       404
5       18271575951     10.168.100.2    www.leotest.com 1527    2106    200
6       84188413        10.168.100.3    www.leotest.com 4116    1432    200
7       13590439668     10.168.100.4                    1116    954     200
8       15910133277     10.168.100.5    www.hao123.com  3156    2936    200
9       13729199489     10.168.100.6                    240     0       200
10      13630577991     10.168.100.7    www.shouhu.com  6960    690     200
11      15043685818     10.168.100.8    www.baidu.com   3659    3538    200
12      15959002129     10.168.100.9    www.leotest.com 1938    180     500
13      13560439638     10.168.100.10                   918     4938    200
14      13470253144     10.168.100.11                   180     180     200
15      13682846555     10.168.100.12   www.qq.com      1938    2910    200
16      13992314666     10.168.100.13   www.gaga.com    3008    3720    200
17      13509468723     10.168.100.14   www.qinghua.com 7335    110349  404
18      18390173782     10.168.100.15   www.sogou.com   9531    2412    200
19      13975057813     10.168.100.16   www.baidu.com   11058   48243   200
20      13768778790     10.168.100.17                   120     120     200
21      13568436656     10.168.100.18   www.alibaba.com 2481    24681   200
22      13568436656     10.168.100.19                   1116    954     200
  • 注:`如果’\t’前所连接内容长度不是8的整数倍,那么’\t’会添加相应的空格数使总长度达到最近的8的整数倍。(phone_data.txt 文档中的空格是用“\t”切分的)

2)输入数据格式

22  13568436656	10.168.100.19      1116    		954  	   200
id	手机号码	 网络ip			上行流量	下行流量	网络状态码

3)期望 输出数据格式

手机号码	上行流量	下行流量	总流量

反序列化顺序要和序列化的传的前后一致

在这里插入图片描述

2.2.2、编写MapReduce程序
1)自定义FlowBean对象实现序列化接口(Writable)

创建一个包com.leojiang.mapreduce.writable,之后创建FlowBean对象

(1)必须实现Writable接口
(2)反序列化时,需要反射调用空参构造函数,所以必须有空参构造
(3)重写序列化方法
(4)重写反序列化方法
(5)注意反序列化的顺序和序列化的顺序完全一致
(6)要想把结果显示在文件中,需要重写toString(),可用”\t”分开,方便后续用。
(7)如果需要将自定义的bean放在key中传输,则还需要实现comparable接口,因为mapreduce框中的shuffle过程一定会对key进行排序。

创建FlowBean

package com.leojiang.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)创建FlowMapper
package com.leojiang.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     10.196.100.1    www.leotest.com 2481    24681   200)
        String line = value.toString();

        // 2、切割
        // (1,13736230513,10.196.100.1,www.leotest.com,2481,2468,200)
        String[] split = line.split("\t");

        // 2、切割 (2)
//        // 去除首尾空格
//        String tline = line.trim();
//        // s表示匹配任何空白字符,+表示匹配一次或多
//        String[] split = tline.split("\\s+");


        // 3、抓取所需的数据: 手机号码	上行流量	下行流量	总流量
        String phone = split[1];
        String up = split[split.length - 3];
        String down = split[split.length - 2];

        // 4、封装输出的kv
        outK.set(phone);
        outV.setUpFlow(Long.parseLong(up));
        outV.setDownFlow(Long.parseLong(down));
        outV.setSumFlow();

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

3)创建FlowReducerr
package com.leojiang.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)创建`FlowDriver类
package com.leojiang.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 FlowDriver {

    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(FlowDriver.class);

        // 3、关联 mapper 和 reducer
        job.setMapperClass(FlowMapper.class);
        job.setReducerClass(FlowReducer.class);

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

        // 设置最终数据输出的key和value类型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(FlowBean.class);
        
        // 5、设置最终数据输出的 key 和 value 类型
        FileInputFormat.setInputPaths(job, new Path("C:\\Leojiang\\leojiangDocument\\Hadoop\\tmp-testFile\\inputflow"));
        FileOutputFormat.setOutputPath(job, new Path("C:\\Leojiang\\leojiangDocument\\Hadoop\\tmp-testFile\\outputflow"));

        // 6、提交job
        boolean result = job.waitForCompletion(true);
        System.exit(result ? 0 : 1);
    }
}

2.2.3、根据测试文件phone_data.txt进行Win本地测试

测试:idea run FlowDriver将获得如下文件:

在这里插入图片描述

如下为我们所需的最终结果:

在这里插入图片描述

注:FlowDriver运行后如果报如下错误:

Job job_local1388601718_0001 failed with state FAILED due to: NA
  • 1、是因为测试文件不是按照\t进行分割的,可以根据空格进行切割。(参考FlowMapper 中 切割(2)
  • 2、也可以自己格式化文件,例如使用notepad++把文件中的空格转换成制表符\t
2.2.4、修改代码动态输入,可以在Linux上运行(参考1.6.4)

3、MapReduce 核心框架原理

在这里插入图片描述

3.1、InputFormat数据输入

1)切片与MapTask并行度决定机制

MapTask的并行度决定Map阶段的任务处理并发度,进而影响整个job的处理速度。

2)MapTask并行度决定机制

数据块block:是HDFS物理上把数据进行分块(0-128MB)。数据块是HDFS存储数据单位

数据切片:数据切片只是逻辑上对输入进行分片,并不会在磁盘上将其切分成片进行存储。数据切片是MapReduce程序计算输入数据的单位一个切片会对应启动一个MapTask

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

在这里插入图片描述

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

在这里插入图片描述

3.1.3、FileInputFormat 切片解析,及切片机制

1、切片解析:

  • 1)程序先找到你数据存储的目录。

  • 2)开始遍历处理(规划切片)目录下的每一个文件

  • 3)遍历第一个文件ss.txt
    (a)获取文件大小fs.sizeOf(ss.txt)
    (b)计算切片大小 computeSplitSize(Math.max(minSize,Math.min(maxSize,blocksize)))=blocksize=128M
    (c)默认情况下,切片大小=blocksize
    (d)开始切,形成第1个切片:ss.txt—0:128M 第2个切片ss.txt—128:256M 第3个切片ss.txt—256M:300M (每次切片时,都要判断切完剩下的部分是否大于块的1.1倍,不大于1.1倍就划分一块切片
    (e)将切片信息写到一个切片规划文件中
    (f)整个切片的核心过程在getSplit()方法中完成
    (g)InputSplit只记录了切片的元数据信息,比如起始位置、长度以及所在的节点列表等。

  • 4)提交切片规划文件到YARN上,YARN上的MrAppMaster就可以根据切片规划文件计算开启MapTask个数。

2、切片机制:

简单的按照文件的内容长度进行切片;切片大小=block大小;切片不考虑数据集整体,而是逐个针对每一个文件单独切片。一个切片会对应启动一个MapTask。

在这里插入图片描述

1、在提交Job时,任务分两种形式运行:本地模式和集群模式,本地模式的块大小是32M。

2、由于默认的TextInputFormat不管文件多小都会占一个切片,当有大量小文件时会严重消耗内存,可用专门用于处理大量小文件的CombineTextInputFormat代替。

3.3.4、TextInputFormat (默认使用)

TextInputFormat 是默认的 FileInputFormat 实现类:当FileInputFormat这个类将文件file切分成block块之后按行读取每条记录,随后将每个split块中的每行记录解析成一个一个的键值对,即<k1,v1>

键是存储该行在整个文件中的起始字节偏移量, LongWritable 类型。值是这行的内容,不包括任何行终止 符(换行符和回车符),Text 类型。

3.3.5、CombinTextInputFormat (多个小文件使用场景)

CombinTextInputFormat 切片机制:主要应用于小文件过多的场景,可以将多个小文件从逻辑上规划到一个切片中,这样,多个小文件就可以交给一个(或多个)MapTask处理。

在这里插入图片描述

CombinTextInputFormat 使用案例实操:

1)创建一个包 com.leojiang.mapreduce.combineTextInputFormat ,之后可以 copy 包wordcount下的所有文件进行复用。

2)随便创建大小不同的小文件(所有文件加起来一共62M)

在这里插入图片描述

3)修改 WordCountDriver 类中的输入输出路径:

代码:

        // 6 设置window本地,输入路径和输出路径 
        FileInputFormat.setInputPaths(job, new Path("C:\\Leojiang\\leojiangDocument\\Hadoop\\tmp-testFile\\inputCombinText"));
        FileOutputFormat.setOutputPath(job, new Path("C:\\Leojiang\\leojiangDocument\\Hadoop\\tmp-testFile\\outputCombinText"));

不做任何处理,运行 WordCount 案例程序,切片数为4.

可以从日志中看出,map 4个Task job,reduce 1个Task job(reduce如果不做设置默认为1)

number of splits:4

4)在WordCountDriver 中添加如下代码,运行程序,观察运行的切片数量。

添加如下代码:

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

        // 如果不设置InputFOrmat, 它默认使用的是TextInputFormat
        job.setInputFormatClass(CombineTextInputFormat.class);

        // 设置虚拟内存切片大小值为20M
        CombineTextInputFormat.setMaxInputSplitSize(job, 20971520);

运行结果为3个切片

number of splits:3

5)修改切片最大值为70M(所有文件加起来一共62M)

        // 设置虚拟内存切片大小值为70M
        CombineTextInputFormat.setMaxInputSplitSize(job, 73400320);

运行结果为1个切片

number of splits:1
3.2、MapReudce 整体工作流程详解

在这里插入图片描述
在这里插入图片描述

上面两张图就是MapReduce的工作流程,下面来用文字叙述一些要注意的点。

a)当客户端将job提交到Yarn集群时,Yarn会开启MrAppMaster,MrAppMaster会读取客户端的提交信息(切片信息、jar、job.xml,如果是本地模式运行则不需要jar,job.xml封装了任务运行时的各种信息)。MrAppMaster读取切片信息并开启对应个数的MapTask。

b)MapTask的处理结果会输送到outputCollector,outputCollector就是环形缓冲区。环形缓冲区事实上就是一个字节数组。

c)环形缓冲区首先会定义一个分割线,然后从该分割线出发,左侧写元数据(索引),右侧写真实数据,待写到mapreduce.map.sort.spill.percent 80%时(默认80%,这个是调优的参数),则将缓冲区内容spill(溢出)到溢写文件,同时以20%空白区域的中间位置为分割线,反向写。

d)关于环形缓冲区的几个问题:

  • 如果溢写速度慢,反向写速度快,则会覆盖之前的内容吗?–> 答案是不会,如果溢写线程速度慢的话,那么反向写线程会pending,等80%空间腾出来之后再继续写

  • 环形缓冲区,他这么设计有什么用呢?解决什么问题呢?

    • 1、环形缓冲区不需要重新申请新的内存,始终用的都是这个内存空间。大家知道MR是用java写的,而Java有一个最讨厌的机制就是Full GC。Full GC总是会出来捣乱,这个bug也非常隐蔽,发现了也不好处理。环形缓冲区从头到尾都在用那一个内存,不断重复利用,因此完美的规避了Full GC导致的各种问题,同时也规避了频繁申请内存引发的其他问题。

    • 2、另外呢,环形缓冲区同时做了两件事情:1、排序;2、索引。在这里一次排序,将无序的数据变为有序,写磁盘的时候顺序写,读数据的时候顺序读,效率高非常多!

  • 为什么不等缓冲区满了再spill? --> 会出现阻塞。

  • 数据的分区和排序是在哪完成的?–> 分区是根据元数据meta中的分区号partition来分区的,排序是在spill的时候排序。

e)一个MapTask对应一个环形缓冲区,一个环形缓冲区会生成多个溢写文件,溢写时会进行快速排序,注意:排序时是在内存中对元数据进行排序,这样就不会移动真实数据位置,溢写时则根据排好序的元数据去寻找指定位置的真实数据。

f)在溢写时,可以进行combiner预聚合,但有前提条件。

g)多个溢写文件再进行归并排序,合并成一个溢写文件,多个分区都在这一个溢写文件中,分区内有序,分区间无序。

h)ReduceTask从多个MapTask中拉取对应分区的文件,再归并排序,有序的好处是能快速地让具有相同key的kv对进入reduce方法。

i)事实上,数据量小的情况下都是MapTask全部执行完毕之后再开启ReduceTask,但是当数据量大时就会等待过久,可以设置等一部分MapTask执行完之后就开启ReduceTask从而提前聚合一部分数据。

j)Shuffle中的缓冲区大小会影响到MapReduce 程序的执行效率,原则上说,缓冲区越大,磁盘io的次数越少,执行速度就越快。缓冲区的大小可以通过参数调整,参数:mapreduce.task.io.sort.mb 默认100M。(可以稍微设置大一些,但不要太大,因为每个spilt就128M。)

3.3、Shuffle机制
3.3.1、Shuffle 机制详解

Map方法之后,Reduce方法之前的数据处理过程称之为Shuffle。

在这里插入图片描述

shuffle过程做的事:分区、排序、合并、压缩。

3.3.2、Partition分区

HashPartitioner:hash 分区是默认分区,根据 key 的 HashCode 对 ReduceTasks 个数取模得到的,用户没法控制谁到哪个分区。

HashPartitioner源码如下:

public class HashPartitioner<K, V> extends Partitioner<K, V> {
    public HashPartitioner() {
    }

    public int getPartition(K key, V value, int numReduceTasks) {
        return (key.hashCode() & 2147483647) % numReduceTasks;
    }
}
分区使用案例实操

1、测试:设置ReduceTasks 个数

我们需要创建一个新的包 com.leojiang.mapreduce.partitioner,之后可以 copy 包wordcount下的所有文件进行复用。

WordCountDriver 类中可以设置ReduceTasks 个数 (默认hash分区)

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

运行可以发现结果生成为两个文件

在这里插入图片描述

2、测试:自定义分区

在这里插入图片描述

1)我们需要创建一个新的包 com.leojiang.mapreduce.partitioner2,之后可以 copy 包Writable 项目(上面创建的序列化项目)下的所有文件进行复用。

2)创建新的类ProvincePartitioner,根据需求进行分区:

package com.leojiang.mapreduce.partitioner2;

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

// FlowMapper 输出对应的KV <Text, FlowBean>     (手机号码,    上行流量	下行流量    总流量)
public class ProvincePartitioner extends Partitioner<Text, FlowBean> {
    @Override
    public int getPartition(Text text, FlowBean flowBean, int numPartitions) {
        // text 是手机号
        String phone = text.toString();
        // 取手机号前三位
        String prePhone = phone.substring(0, 3);

        int partition;

        if("136".equals(prePhone)){
            partition = 0;
        }else if ("137".equals(prePhone)){
            partition = 1;
        }else if ("138".equals(prePhone)){
            partition = 2;
        }else if ("139".equals(prePhone)){
            partition = 3;
        }else {
            partition = 4;
        }

        return partition;
    }
}

3)创建分区完成后,需要在 FlowDriver 类中进行关联

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

        // 建立我们设置的分区类
        job.setPartitionerClass(ProvincePartitioner.class);
        // 我们做了5个分区所以设置为5
        job.setNumReduceTasks(5);

运行结果如下,输出5个文件:

在这里插入图片描述

Tips:我们做了5个分区建立关联后,那么在 job.setNumReduceTasks()时,设置2~4、1、大于5是否可以呢?

  • 小于分区的数量且不等于1:设置2~4不可以的,这样会报ioException
  • 可以设置为1:map会自己new一个分区,不在走我们设置的分区。
  • 大于设置的分区数量:结果则会多产生几个空的输出文件 part-r-000xx
  • 分区号必须从0开始累加:在这里插入图片描述
3.3.3、排序 WritableComparable
1、简述
  • 在MapTask和ReduceTask均会对数据按照key进行排序。该操作属于Hadoop的默认行为。任何应用程序的数据均会被排序,而不管逻辑上是否需要。

  • hadoop 默认的排序,是按照字典顺序进行排序,且实现该排序的方法是快速排序

  • 对于Maptask,它将会处理的结果暂时放到环形缓冲区中,当环形缓冲区使用率达到一定阀值后,在对缓冲区中的数据进行一次快速排序,并将这些有序排序数据溢写在磁盘上,而当数据处理完毕后,它会对磁盘上所有的文件进行归并排序

  • 对ReduceTask,它从每个MapTask上远程拷贝相应的数据文件,如果文件大小超过一定阀值,则溢写在磁盘上,否则存储在内存中。如果磁盘文件数目达到一定阀值,则进行一次合并将数据溢写到磁盘上。当所有数据拷贝完毕后,ReduceTask统一对内存和磁盘上的所有数据进行一次归并排序

2、排序分类
  • 部分排序
    MapReduce根据输入记录的键对数据集排序。保证输出的每个文件内部有序
  • 全排序
    最终输出结果只有一个文件,且文件内部有序。实现方式是只设置一个ReduceTask。但是该方法在处理大型文件时效率很低,因为一台机器要处理所有的文件,完全丧失了MapReduce的并行架构
  • 辅助排序(GroupingComparator分组]) --> 目前不使用
    在Reduce端对key进行分组。应用于:在接受的key为bean对象时,想让一个或者几个字段相同(全部字段比较不相同)的key进入到同一个reduce方法时,可以采用分组排序。
  • 二次排序
    在自定义排序过程中,如果compareTo中的判断条件为两个即二次排序,如果有三个判断条件即三次排序
3、自定义排序 WritableComparable

bean对象作为key传输,需要实现WritableComparable接口重写compareTo方法,就可以实现排序

3.3.4、排序:案例实操 WritableComparable
1、WritableComparable 全排序

需求:根据 (2.2、序列化案例)的产生的结果作为input file,再次对总流量进行倒序排序

在这里插入图片描述

实现过程分析:

  • 1、获取 (2.2、序列化案例)的产生的结果作为输入数据
  • 2、输出数据,按照总流量降序排序
  • 3、FlowBean实现WritableComparable接口重写compareTo方法
  • 4、Mapper类:context.write(bean, 手机号)
  • 5、Reducer类:循环输出即可

1)创建一个新的包路径 com.leojiang.mapreduce.writableComparable ,之后copy 包writable 下的文件进行复用。

2)修改FlowBean类,①实现WritableComparable接口;②重写compareTo方法

package com.leojiang.mapreduce.writableComparable;

import org.apache.hadoop.io.Writable;
import org.apache.hadoop.io.WritableComparable;

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

/**
 * 1、定义类实现writable接口
 * 2、重写序列化和反序列化方法
 * 3、重写空参构造
 * 4、toString方法
 */
// ①实现**WritableComparable**接口
public class FlowBean implements WritableComparable<FlowBean> {
    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;
    }
	// ② 重写compareTo方法
    @Override
    public int compareTo(FlowBean o) {
        // 总流量的倒序排序
        if(this.sumFlow >o.sumFlow){
            return -1;
        }else if(this.sumFlow < o.sumFlow){
            return 1;
        }else {
            return 0;
        }
    }
}

3)重新编写map:FlowMapper

输出的k,v是<FlowBean, Text>,因为我们需要按照总流量进行排序所以需要把FlowBean的值作为key输出。

package com.leojiang.mapreduce.writableComparable;

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, FlowBean, Text> {

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

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

        // 获取一行
        String line = value.toString();
        String[] split = line.split("\t");
        outV.set(split[0]);
        outK.setUpFlow(Long.parseLong(split[1]));
        outK.setDownFlow(Long.parseLong(split[2]));
        outK.setSumFlow();

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

4)重新编写reduce:FlowReducer(输入k,v是<FlowBean, Text>)

package com.leojiang.mapreduce.writableComparable;

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

import java.io.IOException;

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

        for (Text value : values) {
            context.write(value,key);
        }
    }
}

5)复用driver:FlowDriver

  • 仅修改了job.setMap的输出的kv类型,以及文件路径
package com.leojiang.mapreduce.writableComparable;

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 {
        // 1、获取 job
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf);

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

        // 3、关联 mapper 和 reducer
        job.setMapperClass(FlowMapper.class);
        job.setReducerClass(FlowReducer.class);

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

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

        // 5、设置最终数据输出的 key 和 value 类型
        FileInputFormat.setInputPaths(job, new Path("C:\\Leojiang\\leojiangDocument\\Hadoop\\tmp-testFile\\outputflow"));
        FileOutputFormat.setOutputPath(job, new Path("C:\\Leojiang\\leojiangDocument\\Hadoop\\tmp-testFile\\outputflow1"));

        // 6、提交job
        boolean result = job.waitForCompletion(true);
        System.exit(result ? 0 : 1);
    }
}

运行结果:

在这里插入图片描述

6)如果在按照总流量反向排序(这叫二次排序),同时需要按照上行流量正向排序可以修改FlowBean类如下:

    @Override
    public int compareTo(FlowBean o) {
        // 总流量的倒序排序
        if (this.sumFlow > o.sumFlow) {
            return -1;
        } else if (this.sumFlow < o.sumFlow) {
            return 1;
        } else {
            // 按照上线流量正排序
            if (this.upFlow > o.upFlow) {
                return 1;
            } else if (this.upFlow < o.upFlow) {
                return -1;
            } else { 
                //如果再按照下行流量再在下面继续判断即可
                return 0;
            }
        }

    }

运行结果如下:

在这里插入图片描述

2、WritableComparable 区内排序

按照要求分区且每个区内进行排序

1)创建一个新的包路径 com.leojiang.mapreduce.partionerAndWritableComparable ,之后copy 包writableComparable 下的文件进行复用。

2)创建一个ProvincePartitioner2

package com.leojiang.mapreduce.partionerAndWritableComparable;

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

public class ProvincePartitioner2 extends Partitioner<FlowBean, Text> {

    @Override
    public int getPartition(FlowBean flowBean, Text text, int numPartitions) {
        String phone = text.toString();
        String prePhone = phone.substring(0, 3);

        int partition;
        if("136".equals(prePhone)){
            partition = 0;
        }else if ("137".equals(prePhone)){
            partition = 1;
        }else if ("138".equals(prePhone)){
            partition = 2;
        }else if ("139".equals(prePhone)){
            partition = 3;
        }else {
            partition = 4;
        }

        return partition;
    }
}

3)FlowDriver 类中关联ProvincePartitioner2

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

        job.setPartitionerClass(ProvincePartitioner2.class);
        job.setNumReduceTasks(5);

运行结果如下:

在这里插入图片描述

3.3.5、Combiner 合并

Combiner的使用场景:总的来说,为了提升MR程序的运行效率,为了减轻ReduceTask的压力,另外减少IO的开销。

1)Combiner是MR程序中Mapper和Reducer之外的一种组件,执行时机在Mapper结束之后,Reducer开始之前

2)Combiner组件的父类就是Reducer

3)Combiner和Reducer的区别在于运行的位置

  • Combiner是在每一个MapTask所在的节点运行
  • Reducer是接收全局所有Mapper的输出结果;

4)Combiner的意义就是对每一个MapTask的输出进行局部汇总,以减小网络传输量。

5)Combiner 能够应用的前提是不能影响最终的业务逻辑,而且,Combiner的输出kv应该跟Reducer的输入kv类型要对应起来。

  • 场景1、不可以使用

    // 例如求平均值,就不适应
    Mapper
    3 5 7 ->(3+5+7)/3=5 
    2 6 ->(2+6)/2=4
    
    Reducer 
    (3+5+7+2+6)/5=23/5  不等于  (5+4)/2=9/2 
        ()				      (×) 不应使用combiner
    
  • 场景2、求和就可以使用

3.3.5、Combiner 合并实操

1)需求:统计过程中对每一个MapTask的输出进行局部汇总,以减小网络传输量。

方法一:
  • 增加一个WordcountCombiner类继承Reducer
  • 在WordcountCombiner中:1、统计单词汇总;2、将统计结果输出

2)创建一个新的包com.leojiang.mapreduce.combiner, 拷贝包wordcount 下的所有类复用即可。

3)在combiner包下创建一个新的类WordCountCombiner

package com.leojiang.mapreduce.combiner;

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

import java.io.IOException;

public class WordCountCombiner 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;
        for (IntWritable value : values) {
            sum += value.get();

        }

        outV.set(sum);
        context.write(key, outV);
    }
}

4)在WordCountDriver类中添加Combiner的job.setCombinerClass(WordCountCombiner.class)即可:

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

        job.setCombinerClass(WordCountCombiner.class);

运行结果如下,可以看到减少了Reduce阶段的压力。

在这里插入图片描述

Tips:

  • 如果设置job.setNumReduceTasks(0);,就不会有Shuffle阶段和Reducer阶段,数据不会进行聚合,只有map的结果。

在这里插入图片描述

方法二:(推荐)

我们也可以直接将WordCountReducer作为Combiner,在WordCountDriver驱动类中指定即可

不用创建新的l类WordCountCombiner,效果是相同的

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

//        job.setCombinerClass(WordCountCombiner.class);
//        job.setNumReduceTasks(0);
        job.setCombinerClass(WordCountReducer.class);
3.4、OutPutFormat数据输出
3.4.1、OutputFormat接口实现类

OutputFormat是MapReduce输出的基类,所有实现MapReduce输出都实现了 OutputFormat接口。下面我们介绍几种常见的OutputFormat实现类。

  • 1、OutputFormat实现类

在这里插入图片描述

  • 2、默认输出格式是TextOutputFormat

  • 3、自定义TextOutputFormat

    • 1)应用场景例如:输出数据到Mysql、HBase、Elasticsearch等存储框架中

    • 2)自定义OutputFormat步骤

      • 自定一个类继承FileOutputFormat;
      • 改写RecordWriter,具体改下输出的数据方法为write()。
3.3.2、自定义OutputFormat 实操案例

1、需求:过滤输入的日志,包含leo的网站输出到……/leo.log,不包含leo的网站输出到……/other.log

输入数据 log.txt

https://www.baidu.com
https://www.google.com
https://www.google.com
https://www.wallhaven.com
https://www.cainiao.com
https://www.leotest.com
https://www.leotest2.com
https://www.leotest3.com
https://www.leotest4.com
https://www.leotest5.com

2、自定义分析处理流程:可以自定义一个OutputFormat类

  • 创建一个类LogRecordWriter继承RecordWriter
    • a)创建两个文件的输出流:leoOut、otherOut
    • b)如果输入数据包含leo,输出到 leoOut 流,如果不包含输出到 otherOut流
  • 驱动类Driver中进行关联 job.setOutputFormatClass(LogOutPutFormat.class); 即可

3、开始创建一个新的包名 com.leojiang.mapreduce.outputformat

1)创建类 LogMapper

package com.leojiang.mapreduce.outputformat;

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

import java.io.IOException;

public class LogMapper extends Mapper<LongWritable, Text, Text, NullWritable> {
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        // https://www.leotest.com
        // (https://www.leotest.com,NullWritable)
        // 不做任何处理
        context.write(value, NullWritable.get());
    }
}

2)创建类 LogReducer

package com.leojiang.mapreduce.outputformat;

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

import java.io.IOException;

public class LogReducer extends Reducer<Text, NullWritable, Text, NullWritable> {
    @Override
    protected void reduce(Text key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
        // 防止有相同的数据丢失
        for (NullWritable value : values) {
            context.write(key, NullWritable.get());
        }
    }
}

3)自定义OutPutFormat:创建类 LogReducer,根据需求再创建LogRecordWriter类

LogReducer

package com.leojiang.mapreduce.outputformat;

import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.RecordWriter;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

import java.io.IOException;

public class LogOutPutFormat extends FileOutputFormat<Text, NullWritable> {
    @Override
    public RecordWriter<Text, NullWritable> getRecordWriter(TaskAttemptContext job) throws IOException, InterruptedException {

        // 需要一个getRecordWriter 那么我们就自己创建一个LogRecordWriter
        LogRecordWriter lrw = new LogRecordWriter(job);
        return lrw;
    }
}

LogRecordWriter

package com.leojiang.mapreduce.outputformat;

import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.RecordWriter;
import org.apache.hadoop.mapreduce.TaskAttemptContext;

import java.io.IOException;

public class LogRecordWriter extends RecordWriter<Text, NullWritable> {

    private FSDataOutputStream leoOut;
    private FSDataOutputStream otherOut;

    public LogRecordWriter(TaskAttemptContext job) {
        // 1 创建两条流
        try {
            FileSystem fs = FileSystem.get(job.getConfiguration());
            leoOut = fs.create(new Path("C:\\Leojiang\\leojiangDocument\\Hadoop\\tmp-testFile\\outputF\\leo.log"));
            otherOut = fs.create(new Path("C:\\Leojiang\\leojiangDocument\\Hadoop\\tmp-testFile\\outputF\\other.log"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void write(Text key, NullWritable value) throws IOException, InterruptedException {
        String log = key.toString();

        // 2 具体写
        if (log.contains("leotest")) {
            leoOut.writeBytes(log+"\n");
        } else {
            otherOut.writeBytes(log+"\n");
        }
    }

    @Override
    public void close(TaskAttemptContext context) throws IOException, InterruptedException {
        // 3 关闭流
        IOUtils.closeStream(leoOut);
        IOUtils.closeStream(otherOut);
    }
}

4)创建类 LogDriver

package com.leojiang.mapreduce.outputformat;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
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 LogDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        // 1 获取job
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf);

        // 3 设置jar包路径
        job.setJarByClass(LogDriver.class);

        // 3 关联mapper 和 reducer
        job.setMapperClass(LogMapper.class);
        job.setReducerClass(LogReducer.class);

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

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

        // 设置自定义outputformat
        job.setOutputFormatClass(LogOutPutFormat.class);

        // 6 设置window本地,输入路径和输出路径
        FileInputFormat.setInputPaths(job, new Path("C:\\Leojiang\\leojiangDocument\\Hadoop\\tmp-testFile\\inputF"));
        // 虽然我们自定义了Outputformat,但是因为我们的outputformat继承自fileoutputformat
        // 而fileoutputformat要输出一个 _SUCCESS 文件,所以在这还得指定一个输出目录
        FileOutputFormat.setOutputPath(job, new Path("C:\\Leojiang\\leojiangDocument\\Hadoop\\tmp-testFile\\outputF\\outputF11"));

        // 7 提交job
        boolean result = job.waitForCompletion(true);
        System.exit(result ? 0 : 1);
    }
}

运行结果如下:

在这里插入图片描述

3.5、MapReduce内核工作机制
3.5.1、MapTask工作机制

在这里插入图片描述

3.5.2、ReduceTask工作机制

在这里插入图片描述

3.5.3、ReduceTask并行度决定机制

MapTask并行度由切片个数决定,切片个数由输入文件和切片规则决定。

1)设置ReduceTask并行度(个数)

ReuceTask的并行度同样影响整个Job的执行并发度和执行效率,但与MapTask的并行度由切片数决定不同,ReduceTask数量的决定可以直接手动设置。

// 默认值是1,手动设置为5
job.setNumReduceTasks(5);

Tips:

  • 1)ReduceTask=0,表示没有Reduce阶段,输出文件个数和Map个数一致。

  • 2)ReduceTask默认值就是1,所以输出文件个数为一个。

  • 3)如果数据分布不均匀,就有可能在Reduce阶段产生数据倾斜。

  • 4)ReduceTask数量并不是任意设置,还要考虑业务逻辑需求,有些情况下,需要计算全局汇总结果,就只有一个Reducetask(例如全局排序)。

  • 5)具体有多少个ReduceTask,需要根据集群性能而定。

  • 6)如果分区数不是1,但是ReduceTask为1,是否执行分区过程。答案是:不执行分区过程。因为在MapTask的源码中,执行分区的前提是先判断ReduceNumber个数是否大于1,不大于1肯定是不执行的

3.6、Join多种应用

hive spark flink 都有Join

3.6.1、Reduce Join案例实操
1、需求:合并两个文件,期望如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-whOgZEKj-1685329390036)(C:\Leojiang\leojiangDocument\Typora-image-store\image-20230518150802986.png)]

order.txt: id pid amount (\t 做分割)

1001	01	1
1002	02	2
1003	03	3
1004	01	4
1005	02	5
1006	03	6

pd.txt: pid name(\t 做分割)

01	小米
02	华为
03	格力

期望输出格式 (\t 做分割)

#	id   pname   amount
eg:1001	小米	   ###
2、创建一个新的包com.leojiang.mapreduce.reduceJoin ,然后在其下面创建tableBean类序列化
package com.leojiang.mapreduce.reduceJoin;

import org.apache.hadoop.io.Writable;

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

public class tableBean implements Writable {
    //     order.txt: id pid amount
//     pd.txt:    pid name
    // 1、
    private String id;     // 订单id
    private String pid;    // 商品id
    private int amount; // 商品数量
    private String pname;  // 商品名称
    private String flag;   // 标记是来自哪张表

    // 2、生成空参构造、getset方法
    public tableBean() {
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getPid() {
        return pid;
    }

    public void setPid(String pid) {
        this.pid = pid;
    }

    public int getAmount() {
        return amount;
    }

    public void setAmount(int amount) {
        this.amount = amount;
    }

    public String getPname() {
        return pname;
    }

    public void setPname(String pname) {
        this.pname = pname;
    }

    public String getFlag() {
        return flag;
    }

    public void setFlag(String flag) {
        this.flag = flag;
    }

    @Override
    public void write(DataOutput out) throws IOException {
        // 3、write 序列化
        out.writeUTF(id);
        out.writeUTF(pid);
        out.writeInt(amount);
        out.writeUTF(pname);
        out.writeUTF(flag);
    }

    @Override
    public void readFields(DataInput in) throws IOException {
        // 4、read 反序列化
        this.id = in.readUTF();
        this.pid = in.readUTF();
        this.amount = in.readInt();
        this.pname = in.readUTF();
        this.flag = in.readUTF();
    }

    // 5、生成toString,再做格式修改
    @Override
    public String toString() {
        // id   pname   amount
        return id + '\t' + pname + '\t' + amount;
    }
}
3、创建map类:tableMapper
package com.leojiang.mapreduce.reduceJoin;

import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileSplit;

import java.io.IOException;

public class tableMapper extends Mapper<LongWritable, Text, Text, tableBean> {

    private String fileName;
    private Text outK = new Text();
    private tableBean outV = new tableBean();

    @Override
    protected void setup(Context context) throws IOException, InterruptedException {
        // 初始化
        FileSplit split = (FileSplit) context.getInputSplit();
        fileName = split.getPath().getName();
    }

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

        // 判断是哪个文件的
        if (fileName.contains("order")) {// 处理的是订单表 (id pid amount)
            // 切割 (1)
            String[] split = line.split("\t");

            // 封装 k v
            outK.set(split[1]);
            outV.setId(split[0]);
            outV.setPid(split[1]);
            outV.setAmount(Integer.parseInt(split[2]));
            outV.setPname("");
            outV.setFlag("order");

        } else {// 处理的pd表 (pid name)
            String[] split = line.split("\t");

            outK.set(split[0]);
            outV.setId("");
            outV.setPid(split[0]);
            outV.setAmount(0);
            outV.setPname(split[1]);
            outV.setFlag("pd");
        }
        // 写出
        context.write(outK, outV);
    }
}
4、创建reduce类:tableReducer
package com.leojiang.mapreduce.reduceJoin;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;

public class tableReducer extends Reducer<Text, tableBean, tableBean, NullWritable> {
    @Override
    protected void reduce(Text key, Iterable<tableBean> values, Context context) throws IOException, InterruptedException {
        // pid pname amount flag
        ArrayList<tableBean> orderBeans = new ArrayList<>();
        tableBean pdBean = new tableBean();

        // 循环遍历
        for (tableBean value : values) {

            if ("order".equals(value.getFlag())) {// 处理的是order表

                // *** value临时赋值给tmptableBean,每次new一个地址,来防止数据地址覆盖(因为我们是按照pid为key进行map的,所以这个value会有多条)
                tableBean tmptableBean = new tableBean();
                try {
                    BeanUtils.copyProperties(tmptableBean, value);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }

                orderBeans.add(tmptableBean);

            } else {// 处理的是pd表

                try {
                    BeanUtils.copyProperties(pdBean, value);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        }

        // 循环遍历orderBeans,赋值pdname
        for (tableBean orderBean : orderBeans) {

            orderBean.setPname(pdBean.getPname());
            context.write(orderBean, NullWritable.get());
        }

    }
}
5、创建driver类:tableDriver
package com.leojiang.mapreduce.reduceJoin;

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 tableDriver {
    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(tableDriver.class);

        // 3、关联 mapper 和 reducer
        job.setMapperClass(tableMapper.class);
        job.setReducerClass(tableReducer.class);

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

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

        // 6、设置最终数据输出的 key 和 value 类型
        FileInputFormat.setInputPaths(job, new Path("C:\\Leojiang\\leojiangDocument\\Hadoop\\tmp-testFile\\inputjoin"));
        FileOutputFormat.setOutputPath(job, new Path("C:\\Leojiang\\leojiangDocument\\Hadoop\\tmp-testFile\\outputjoin"));

        // 7、提交job
        boolean result = job.waitForCompletion(true);
        System.exit(result ? 0 : 1);
    }
}
  • 如果运行时因为输入文件没有按照\t切割。可以参考 1.6.2 中按照空格切割的方法

结果如下:

在这里插入图片描述

  • 缺点:这种方式中,合并的操作是在Reduce阶段完成,Reduce端的处理压力太大,Map节点的运算负载则很低,资源利用率不高,且在Reduce阶段极易产生数据倾斜。解决方案:Map端实现数据合并
3.6.2、Map Join案例实操 (需求和3.6.1相同)

使用场景:Map Join 适用于一张表十分小、一张表很大的场景。这样可以将小表存放在内存中。

分析:

  • Driver缓存文件

    1、加载缓存数据

    2、Map端join的逻辑不需要Reduce阶段,因此设置ReduceTask数量为0则可以不进入Reduce阶段,减少了非常耗时的Shuffle过程。

  • 读取缓存的文件数据

    1、在setup()方法中:获取缓存的文件;循环读取文件数据;对数据进行切割;缓存数据到集合当中;最后关闭流。

    2、在map方法中:获取行数据进行切割;获取订单id;获取商品名称;然后进行拼接;最后写出。

1、创建一个新的包com.leojiang.mapreduce.mapJoin, 创建MapJoinMapper类
package com.leojiang.mapreduce.mapJoin;

import org.apache.commons.lang3.StringUtils;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.HashMap;

public class MapJoinMapper extends Mapper<LongWritable, Text, Text, NullWritable> {
    private HashMap<String, String> pdMap = new HashMap<>();
    private Text outK = new Text();

    @Override
    protected void setup(Context context) throws IOException, InterruptedException {
        // 获取缓存的文件,并把pd.txt文件的内容封装到集合 (正常是获取路径下所有的文件,因为我们指定了一个所以[]只有一个文件)
        URI[] cacheFiles = context.getCacheFiles();

        // 读取文件
        FileSystem fs = FileSystem.get(context.getConfiguration());
        FSDataInputStream fis = fs.open(new Path(cacheFiles[0]));

        // 从流中读取数据
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(fis, "UTF-8"));

        // 获取数据
        String line;
        while (StringUtils.isNotEmpty(line = bufferedReader.readLine())) {
            // 切割
            String[] fields = line.split("\t");

            // 赋值
            pdMap.put(fields[0], fields[1]);
        }
        // 关流
        IOUtils.closeStream(bufferedReader);
    }

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        // 处理order.txt
        String line = value.toString();
        String[] fields = line.split("\t");

        // 获取pid
        String pname = pdMap.get(fields[1]);

        // 获取订单id 和 订单数量
        outK.set(fields[0] + "\t" + pname + "\t" + fields[2]);

        context.write(outK, NullWritable.get());
    }
}
2、创建MapJoinDriver类
package com.leojiang.mapreduce.mapJoin;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
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;
import java.net.URI;
import java.net.URISyntaxException;

public class MapJoinDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException, URISyntaxException {
        // 1、获取 job
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf);

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

        // 3、关联 mapper
        job.setMapperClass(MapJoinMapper.class);

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

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

        // 加载缓存数据
        job.addCacheFile(new URI("file:///C:/Leojiang/leojiangDocument/Hadoop/tmp-testFile/inputmapjoin/pd/pd.txt"));
        // Map端Join的逻辑下不需要Reduce阶段,设置reduceTask数量为0
        job.setNumReduceTasks(0);

        // 6、设置最终数据输出的 key 和 value 类型
        FileInputFormat.setInputPaths(job, new Path("C:\\Leojiang\\leojiangDocument\\Hadoop\\tmp-testFile\\inputmapjoin\\order"));
        FileOutputFormat.setOutputPath(job, new Path("C:\\Leojiang\\leojiangDocument\\Hadoop\\tmp-testFile\\outputmapjoin"));

        // 7、提交job
        boolean result = job.waitForCompletion(true);
        System.exit(result ? 0 : 1);
    }
}

结果和ReduceJoin的一致:

在这里插入图片描述

3.7、数据清洗 (ETL)

ETL,是英文Extract-Transform-Load的缩写,用来描述将数据从来源端经过抽取(Extract)、转换(Transform)、加载(Load)至目的端的过程。ETL一词较常用在数据仓库,但其对象并不限于数据仓库

在运行核心业务MapReduce程序之前,往往要先对数据进行清洗,清理掉不符合用户要求的数据。清理的过程往往只需要运行Mapper程序,不需要运行Reduce程序

1、需求:

去除日志中字段个数小于等于 7 的日志

为了方便测试,我从hadoop中截取出一部分log供测试使用: hadoop.txt (一共26行)

2023-02-21 12:04:22,841 INFO org.apache.hadoop.hdfs.server.namenode.NameNode: registered UNIX signal handlers for [TERM, HUP, INT]
2023-02-21 12:04:22,911 INFO org.apache.hadoop.hdfs.server.namenode.NameNode: createNameNode []
2023-02-21 12:04:23,013 INFO org.apache.hadoop.metrics2.impl.MetricsConfig: Loaded properties from hadoop-metrics2.properties
2023-02-21 12:04:23,098 INFO org.apache.hadoop.metrics2.impl.MetricsSystemImpl: Scheduled Metric snapshot period at 10 second(s).
2023-02-21 12:04:23,099 INFO org.apache.hadoop.metrics2.impl.MetricsSystemImpl: NameNode metrics system started
2023-02-21 12:04:23,123 INFO org.apache.hadoop.hdfs.server.namenode.NameNodeUtils: fs.defaultFS is hdfs://hadoop1:10082
2023-02-21 12:04:23,124 INFO org.apache.hadoop.hdfs.server.namenode.NameNode: Clients should use hadoop1:10082 to access this namenode/service.
2023-02-21 12:04:23,256 INFO org.apache.hadoop.util.JvmPauseMonitor: Starting JVM pause monitor
2023-02-21 12:04:23,276 INFO org.apache.hadoop.hdfs.DFSUtil: Filter initializers set : org.apache.hadoop.http.lib.StaticUserWebFilter,org.apache.hadoop.hdfs.web.AuthFilterInitializer
2023-02-21 12:04:23,280 INFO org.apache.hadoop.hdfs.DFSUtil: Starting Web-server for hdfs at: http://hadoop1:10870
2023-02-21 12:04:23,290 INFO org.eclipse.jetty.util.log: Logging initialized @931ms to org.eclipse.jetty.util.log.Slf4jLog
2023-02-21 12:04:23,377 INFO org.apache.hadoop.security.authentication.server.AuthenticationFilter: Unable to initialize FileSignerSecretProvider, falling back to use random secrets.
2023-02-21 12:04:23,386 INFO org.apache.hadoop.http.HttpRequestLog: Http request log for http.requests.namenode is not defined
2023-02-21 12:04:23,393 INFO org.apache.hadoop.http.HttpServer2: Added global filter 'safety' (class=org.apache.hadoop.http.HttpServer2$QuotingInputFilter)
2023-02-21 12:04:23,395 INFO org.apache.hadoop.http.HttpServer2: Added filter static_user_filter (class=org.apache.hadoop.http.lib.StaticUserWebFilter$StaticUserFilter) to context hdfs
2023-02-21 12:04:23,395 INFO org.apache.hadoop.http.HttpServer2: Added filter static_user_filter (class=org.apache.hadoop.http.lib.StaticUserWebFilter$StaticUserFilter) to context static
2023-02-21 12:04:23,395 INFO org.apache.hadoop.http.HttpServer2: Added filter static_user_filter (class=org.apache.hadoop.http.lib.StaticUserWebFilter$StaticUserFilter) to context logs
2023-02-21 12:04:23,398 INFO org.apache.hadoop.http.HttpServer2: Added filter AuthFilter (class=org.apache.hadoop.hdfs.web.AuthFilter) to context hdfs
2023-02-21 12:04:23,398 INFO org.apache.hadoop.http.HttpServer2: Added filter AuthFilter (class=org.apache.hadoop.hdfs.web.AuthFilter) to context static
2023-02-21 12:04:23,398 INFO org.apache.hadoop.http.HttpServer2: Added filter AuthFilter (class=org.apache.hadoop.hdfs.web.AuthFilter) to context logs
2023-02-21 12:04:23,428 INFO org.apache.hadoop.http.HttpServer2: addJerseyResourcePackage: packageName=org.apache.hadoop.hdfs.server.namenode.web.resources;org.apache.hadoop.hdfs.web.resources, pathSpec=/webhdfs/v1/*
2023-02-21 12:04:23,436 INFO org.apache.hadoop.http.HttpServer2: Jetty bound to port 10870
2023-02-21 12:04:23,437 INFO org.eclipse.jetty.server.Server: jetty-9.4.40.v20210413; built: 2021-04-13T20:42:42.668Z; git: b881a572662e1943a14ae12e7e1207989f218b74; jvm 1.8.0_361-b09
2023-02-21 12:04:23,459 INFO org.eclipse.jetty.server.session: DefaultSessionIdManager workerName=node0
2023-02-21 12:04:23,460 INFO org.eclipse.jetty.server.session: No SessionScavenger set, using defaults
2023-02-21 12:04:23,461 INFO org.eclipse.jetty.server.session: node0 Scavenging every 660000ms
2、需求分析

在Map阶段对输入的数据根据规则进行过滤清洗即可

3、代码实现
1)创建一个包com.leojiang.mapreduce.etl,创建HadoopLogMapper类
package com.leojiang.mapreduce.etl;

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

import java.io.IOException;

public class HadoopLogMapper extends Mapper<LongWritable, Text, Text, NullWritable> {
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {

        // 1、获取一行
        String line = value.toString();

        // 2、ETL 创建一个方法成功写出,失败不做任何处理
        boolean result = parseLog(line, context);
        if (!result){
            return;
        }

        // 4、写出
        context.write(value, NullWritable.get());

    }

    private boolean parseLog(String line, Context context) {
        // 3、切割
        // 2023-02-21 12:04:23,461 INFO org.eclipse.jetty.server.session: node0 Scavenging every 660000ms
        String[] fields = line.split(" ");

        // 判断日志的长度是否大于7
        if (fields.length > 7){
            return true;
        }else {
            return false;
        }
    }
}
2)创建HadoopLogDriver类
package com.leojiang.mapreduce.etl;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
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 HadoopLogDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {

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

        // 3 设置jar包路径
        job.setJarByClass(HadoopLogDriver.class);

        // 3 关联mapper
        job.setMapperClass(HadoopLogMapper.class);

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

        // 设置reduce为0
        job.setNumReduceTasks(0);

        // 6、输入输出路径需要根据自己电脑上的实际输出输入路径进行设置
        args = new String[]{"C:/Leojiang/leojiangDocument/Hadoop/tmp-testFile/log", "C:/Leojiang/leojiangDocument/Hadoop/tmp-testFile/logoutput"};

        // 直接设置window本地,输入路径和输出路径
//        FileInputFormat.setInputPaths(job, new Path("C:\\Leojiang\\leojiangDocument\\Hadoop\\tmp-testFile\\log"));
//        FileOutputFormat.setOutputPath(job, new Path("C:\\Leojiang\\leojiangDocument\\Hadoop\\tmp-testFile\\logoutput"));
        // 设置linux 使用
        FileInputFormat.setInputPaths(job, new Path(args[0]));
        FileOutputFormat.setOutputPath(job, new Path(args[1]));

        // 7 提交job
        boolean result = job.waitForCompletion(true);
        System.exit(result ? 0 : 1);
    }
}
结果如下:

可以看到过滤完成后剩余23行log (一共26行)。

在这里插入图片描述

3.8、MapReduce 开发总结

在这里插入图片描述

1、InputFormat

  • 1)默认是TextInputformat kv key 偏移量,v:一行内容
  • 2)处理批量小文件使用CombineTextInputFormat 把多个文件合并到一起统一切片

2、Mapper

  • 1)setup() 初始化: map()用户的业务逻辑; clearup()关闭资源;

3、分区

  • 1)默认分区HashPartitioner,默认按照key的hash值%NumReduceTasks个数

4、排序

  • 1)部分排序:每个输出的文件内部有序;

  • 2)全排序:就一个reduce,对所有的数据大排序

  • 3)二次排序:自定义排序范畴,实现一个writableCompare接口,重写compareTo方法

    总流量倒序 按照上行流量正序

5、Combiner

  • 前提条件:不影响最终的业务逻辑(求和没问题;求平均值会影响最终值)
  • 提前预聚合map => 解决数据倾斜的一个方法;

6、Reducer

  • 用户的业务逻辑:

    setup()初始化;reduce()用户的业务逻辑;cleanup()关闭资源

7、OutputFormat

  • 1)默认TextOutputFormat,按行输出到文件
  • 2)自定义

4、压缩

1、压缩的好处和坏处

压缩的优点:以减少磁盘IO、减少磁盘储存空间;

压缩的缺点:增加CPU开销。

2、压缩的原则:

运算密集型的job,少用压缩;

IO密集型的job,多用压缩。(处理完就需要传走的)

1、MR支持的压缩编码
1)压缩算法对比介绍
压缩格式Hadoop自带?算法文件扩展名是否可切片换成压缩格式后,
原来的程序是否需要修改
DEFLATE是,直接使用DEFLATE.deflate和文本处理一样,不需要修改
Gzip是,直接使用DEFLATE.gz和文本处理一样,不需要修改
bzip2是,直接使用bzip2.bz2和文本处理一样,不需要修改
LZO否,需要安装LZO.lzo需要建索引,还需要指定输入格式
Snappy(3.x以后)是,直接使用Snappy.snappy和文本处理一样,不需要修改
2)压缩性能比较
压缩算法原始文件大小压缩文件大小压缩速度解压速度优缺点
gzip8.3GB1.8GB17.5MB/s58MB/s优点:压缩率比较高
缺点:不支持 Split(切片),压缩/解压速度一般
bzip28.3GB1.1GB2.4MB/s9.5MB/s优点:压缩率高,支持 Split
缺点:压缩/解压速度慢
LZO8.3GB2.9GB49.3MB/s74.6MB/s优点:压缩/解压速度比较快,支持 Split
缺点:压缩率一般,支持切片需要额外创建索引
Snappy250 MB/s500 MB/s优点:压缩和解压速度快
缺点:不支持 Split,压缩率一般
3)压缩位置选择

压缩可以在 MapReduce 作用的任意阶段启用

在这里插入图片描述

4)压缩参数配置

1、为了支持多种压缩/解压算法,Hadoop 引入了编码/解码器

压缩格式对应的编码/解码器
DEFLATEorg.apache.hadoop.io.compress.DefaultCodec
gziporg.apache.hadoop.io.compress.GzipCodec
bzip2org.apache.hadoop.io.compress.BZip2Codec
LZOcom.hadoop.compression.lzo.LzopCodec
Snappyorg.apache.hadoop.io.compress.SnappyCodec

2、要在 Hadoop 中启用压缩,可以配置如下参数

参数默认值阶段建议
io.compression.codecs
(在core-site.xml中配置)
org.apache.hadoop.io.compress.DefaultCodec, org.apache.hadoop.io.compress.GzipCodec, org.apache.hadoop.io.compress.BZip2Codec输入压缩Hadoop使用文件扩展名判断是否支持某种编解码器
mapreduce.map.output.compress
(在mapred-site.xml中配置)
falsemapper输出这个参数设为true启用压缩
mapreduce.map.output.compress.codec
(在mapred-site.xml中配置)
org.apache.hadoop.io.compress.DefaultCodecmapper输出企业多使用LZO或Snappy编解码器在此阶段压缩数据
mapreduce.output.fileoutputformat.compress
(在mapred-site.xml中配置)
falsereducer输出这个参数设为true启用压缩
mapreduce.output.fileoutputformat.compress.codec
(在mapred-site.xml中配置)
org.apache.hadoop.io.compress. DefaultCodecreducer输出使用标准工具或者编解码器,如gzip和bzip2
mapreduce.output.fileoutputformat.compress.type
(在mapred-site.xml中配置)
RECORDreducer输出SequenceFile输出使用的压缩类型:NONE和BLOCK
2、压缩实操

1)创建包com.leojiang.mapreduce.compress,然后可以拷贝wordcount包下的所有类进行复用。

2)Map输出端采用压缩

WordCountDriver

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

        // 开启map端输出压缩
        conf.setBoolean("mapreduce.map.output.compress", true);
        // 设置map端输出压缩方式
        conf.setClass("mapreduce.map.output.compress.codec", BZip2Codec.class, CompressionCodec.class);

        Job job = Job.getInstance(conf);

3)Reduce输出端采用压缩

WordCountDriver

        // 设置reduce端输出压缩开启
        FileOutputFormat.setCompressOutput(job, true);
        // 设置压缩的方式
        FileOutputFormat.setOutputCompressorClass(job, BZip2Codec.class);
//        FileOutputFormat.setOutputCompressorClass(job, GzipCodec.class);
//        FileOutputFormat.setOutputCompressorClass(job, DefaultCodec.class);

        // 7 提交job

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

三、Hadoop_Yarn

Hadoop_Yarn

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值