Hadoop学习 (MapReduce 过程详解及其性能优化

MapReduce 过程详解及其性能优化

ok. 之前一篇 Hadoop 的HDFS笔记, 真是出乎我的意料!! HDFS学习
一夜之间尽然有一千访问!! 太强了吧… 感谢认可,2021年第一天, 继续加油, 搞完Hadoop其它的入门笔记!!

Hadoop 核心-MapReduce

首先让我们来重温一下 hadoop 的四大组件:

  • HDFS: 分布式存储系统
    MapReduce: 分布式计算系统
    YARN: hadoop 的资源调度系统
    Common: 以上三大组件的底层支撑组件,主要提供基础工具包和 RPC 框架等

MapReduce是一个分布式运算程序的编程框架,是用户开发“基于 hadoop 的数据分析 应用”的核心框架

  • 对于大量数据的计算, 通常采用的处理方法就是并行计算
    这就要求将大型而复杂的计算问题分解为,各个子任务 并分配到多个计算资源下 同时进行计算。

  • 其显著特点是耗时小于单个计算资源下的计算。

  • 对多数开发人员来说,并行计算还是个陌生、复杂的东西 尤其是涉及 分布式的问题将会更加棘手。
    MapReduce 就是一种实现并行计算 的编程模型,它向用户提供接口,屏蔽了并行计算 特别是分布式处理的诸多细节。

  • 让那些没有多少并行计算经验的开发人, 方便应用使用!

MapReduce由两个概念合并而来:

  • Map映射 和 Reduce规约合并 (中间还有一个Shuffle)
    map 映射,负责将任务分解成多个子任务…
    reduce 归约,负责把分解后多个任务的 处理结果 进行汇总…

MapReduce概述

Hadoop 的MapReduce框架,源自Google的 MapReduce论文;
MapReduce是一种编程模型

  • MapReduce的最大成就是重写了Google的索引文件系统。
    现在MapReduce被广泛地应用于: 日志分析、海量数据排序、在海量数据中查找特定模式等场景中。

Hadoop中的并行应用程序的开发是基于MapReduce编程模型的

MapReduce 的架构设计:

MapReduce v1

**与HDFS架构设计相似,在Hadoop中用于执行MapReduce也有两个角色: JobTracker TaskTracker (主/从架构…) **
Job MapReduce程序编写完成后需要配置成一个MapReduce作业
Task 作业中的任务,分为两类:MapTask和ReduceTask

JobTracker Tracker 中译:追踪器,调度器

  • 是一个Master 主服务,用于作业(Job) 的管理和调度工作,
    一个Hadoop集群中只有一台JobTracker, 一般情况应该把它部署在单独的机器上。
  • JobTracker 负责创建、调度作业中的每个子任务 ( MapTask(分解执行)ReduceTask(规约汇总)), 运行于TaskTracker上
    并监控它们,如果发现有失败的任务就重新运行它。

TaskTracker

  • 是运行于多个节点上的Slave 从服务,用于执行任务的操作。TaskTracker 需要运行在HDFS的DataNode上。

MapReduce v2 (YARN)

第二代MapReduce框架YARN
实际上之前的所有MapReduce作业均是运行在YARN框架上的。
由于在MapReduce V1中对于超过40000 个节点的大型集群,开始面临扩展性的瓶颈。 其主要表现在: ↓↓↓

  • (1) JobTracker 单点瓶颈。随着集群的数量和提交Job的数量不断增加,
    导致JobTracker的任务量随之增加,最终成为集群的单点瓶颈。
  • (2) TaskTracker端,由于作业分配信息过于简单,
    有可能将多个资源消耗多或运行时间长的 Task 分配到同一个(Node上)这样会造成 作业(Job)的等待时间过长。
  • (3) 作业延迟高。 在MapReduce 运行作业之前,需要TaskTracker 汇报自己的资源运行情况,
    JobTracker 根据获取的信息分配任务,TaskTraker 获取任务之后再开始运行。这样的结果导致小作业启动时间过长。
  • (4) 编程框架不够灵活,虽然MapRetree VI框架允许用户自定义各阶段的对象和处理方法,但并不是最优处理…

Yarn 构建:

  • MapReduce程序编写完成后需要配置成一个MapReduce作业:
    将JobTracker承担的两大块任务: 集群资源管理作业管理
    分离为管理集群上资源使用的资源管理器:(ResourceManager)
    和管理集群上运行任务 MapReduce作业 生命周期的 应用主体(ApplicationMaster)
    然后TaskTracker演化成节点管理器(NodeManager).

YARN仍然是Master/Slave结构 全局的ResoureManager和局部的NodeManager组成了数据计算框架

  • (1) 资源管理器:包括两个功能组件 调度器应用管理器。
    调度器 仅负责协调集群上计算资源的分配,不负责监控各个应用的执行情况。
    应用管理器 负责接收作业,协商获取第一个资源容器用于启动作业所属的应用主体并监控应用主体的存在情况。
  • (2)节点管理器  负责启动和监视集群中机器上的计算资源容器(Container )。
  • (3)应用主体 :应用主体与应用一 一 对应, 负责协调运行MapReduce作业的任务,它和MapReduce 任务都在资源容器中运行。
  • (4)资源容器: 对节点自身内存、CPU、磁盘、网络带宽等资源的抽象封装,
    由资源管理器分配 —>----> 并由节点管理器进行管理。
    它的主要职责是运行、保存或传输应用主体提交的作业或需要存储和传输的数据。

YARN配置:安装配置目录:Yarn配置
注意:
使用YARN 需要单独启动sbin/目录下: star-yarn.sh
Linux上 JPS 命令 可以查看到:
NameNode上出现 ResourceManager 进程表示已成功运行,相应地在 DataNode 上会出现NdeManager 进程

总结:

基于JobTracker 和 TaskTracker的运行架构为MapReduce v1
MapReduce v2中,v1架构已被YARN替代。
.

  • 不论是V1还是V2,都不会影响我们编写MpReduce程序,
    好比同样的一个Web应用, 运行在TomcatJetty下的效果是相同的。
    由此可见: 实际上运行 MapReudce 的过程对开发人员是透明的。

MapReduce 的编程模型:

Hadoop学习 (MapReduce 过程详解及其性能优化_第1张图片

Input 输入

  • 当一个文件上传至 HDFS上时候, 由JobTracker 创建该Job(工程) , 并根据Job 的输入计算输入分片(Input Split)。
    待处理的数据集可以分解成许多小的数据集,且每个小的数据集都可以完全并行地进行处理。
    如果单个文件超过HDFS默认块大小(64MB), 则将按块大小进行分割。

Split 切割

  • 作业调度器(JobTracker) 获取Job的输入分片信息,对输入分片中的记录按照一定规则解析成键值对,
    键(key) 是每行的起始位置,以字节为单位,
    值(value) 是本行文本内容。
    最后每个分片创建一个MapTask并分配到某个TaskTracker。
    input 和 Split 并不是开发者关心的操作, 是Hadoop底层完成的,..
    Spilt给input拆分的多个文件, 以 的形式解析,并创建出了MapTask分配到对应TaskTracker 上实现了 并行开发~

Map 底层有默认实现,也可以用户自定义!

  • TaskTracker 开始执行MapTask.处理输入的每个键值对。
    并对 < K1 V1 >解析成了新的 < K2 V2 > 保存在本地

Shuffle 洗牌

  • 将MapTask的输出转换为ReduceTask的输入的过程。
    洗牌:它会对Map阶段产生的 < K2 V2 > 进一步的解析, 分区 排序 规约 分组 进而产生一个 新的 < K2 V2 >
    一定程度上减轻了 Reduce 的操作, 如果没有这个 Reduce 需要给多个Map 数据进行处理, 占用程序性能

Reduce

  • 读取Shuffing阶段的输出,开始执行ReduceTask处理输入的每个键值对。
    同样,如何处理取决于该阶段的程序代码(开发者根据需求自定义!)最后输出最终结果 < K3 V3 > 。

总结:
在Hadoop中每个MapReduce计算任务都会被初始化为一个Job。
其中主要有两个处理阶段:map阶段和reduce阶段,
两个阶段均以键值对作为输入,然后产生同样为形式的输出。
两个阶段分别对应map( )和reduce( )方法,
这便是开发人员需要实现的两个最重要的阶段和方法,而其他阶段大多可由系统自动处理。

举例DemoHadoop学习 (MapReduce 过程详解及其性能优化_第2张图片

目标:统计一个文件中, 每个单词的个数…大数据的常规分析操作

1 用户上传一个文件 input a.txt文件 上传至 HDFS(文件系统上)
2 Hadoop 的底层就会自动的给其进行 Spit 拆分 多个文件块,并通过 TextInputFormat 文件读取格式流~ 解析成一个 K1 V1

  • 键(key) 是每行的起始位置,以字节为单位,值(value) 是本行文本内容。每一个块对应一个 MapReduce操作

3 在通过,MapReduce的 Map 操作进一步的解析成为一个 K2 V2 的资源…

  • 根据某只逻辑,进行解析成为一个:Hellow,1 Word,1 Hollow,1… 不管这个单词有几个都是 1;

4 经过Map 阶段之后, 就是进入 Shuffle阶段的: 分区 排序 规约 分组 (后面详细介绍~)

  • 之后就会产生一个新的 K2 V2 : (键)Hellow,(值)<1,1> 表示这一阶段中 Hellow 出现了几次~ Reduce之前做的一次汇总,减轻其压力~

5 最后在到 Reduce阶段 做最后的 汇总输出!


Hadoop数据类型

IntWritable 整型数。

LongWritable 长整型数。

FloatWritable 浮点数。

DoubleWritable 双字节数。

BooleanWritable 布尔型。

ByteWritable 单字节, 对应bye类型。

BytesWritable 字节数组, 对应byte [].

Hadoop常用的其他数据类型如下:

Text 使用UTF8格式存储的文本,对String类型的重写。

ObjectWritable 是一种对多类型的封装,可以用于Java的基本类型.如String等。如同Java的Object类。

NullWritable 是一个点位符,序列化长度为零。 当 < key,value > 中的key 或 value 为空时使用

ArrayWritable 针对数组的数据类型,

MapWritable 对java. util.Map的实现。

Hadop数据类型都实现了Writable接口,并且与 Jave 类型之间可相互转化,代码如下所示:

IntWritable num = new IntWritable(1); 		//Java类型转Hadoop类型方式1
num.set(2);									//Java类型转Hadoop类型方式2
int= num.get();							//Hadoop 类型转Java类型,当为Text类型时需使用toString ()方法

既然Hadoop基本数据类型与Java 基本类型之间是互通的,
为什么不直接使用Java基本数据型而是重新定义一套数据类型呢?

在Hadoop中,序列化处于核心地位。
无论是存储文件还是在计算中传输数据都需要执行序列化的过程。
序列化反序列化的速度、 序列化后的数据大小都会影响数据传输的速度, 以致影响计算的效率。

  • 正是因为这些原因,Hadoop并没有采用Java提供的序列化机制,而是重新写了一套,
    由此所有 的Hadoop 基本数据类型都实现了 org.apache.hadoop.io.Writable接口

MapReduce 应用开发

首先启动Hadoop 的服务线程: start-all.sh

Hadoop学习 (MapReduce 过程详解及其性能优化_第3张图片

在文件系统上, 上传一个文件 对其进行解析操作~Hadoop学习 (MapReduce 过程详解及其性能优化_第4张图片

测试文件内容:

hello,world,hadoop 
hive,sqoop,flume,hello 
kitty,tom,jerry,world 
hadoop 

Hadoop学习 (MapReduce 过程详解及其性能优化_第5张图片

IDEA创建一个普通的Maven 工程:

Hadoop学习 (MapReduce 过程详解及其性能优化_第6张图片

导入依赖 pom.xml :

	
	<packaging>jarpackaging>
	
    <dependencies>
        <dependency>
            <groupId>org.apache.hadoopgroupId>
            <artifactId>hadoop-commonartifactId>
            <version>2.7.5version>
        dependency>
        <dependency>
            <groupId>org.apache.hadoopgroupId>
            <artifactId>hadoop-clientartifactId>
            <version>2.7.5version>
        dependency>
        <dependency>
            <groupId>org.apache.hadoopgroupId>
            <artifactId>hadoop-hdfsartifactId>
            <version>2.7.5version>
        dependency>
        <dependency>
            <groupId>org.apache.hadoopgroupId>
            <artifactId>hadoop-mapreduce-client-coreartifactId>
            <version>2.7.5version>
        dependency>
    dependencies>

            <plugin>
                <groupId>org.apache.maven.pluginsgroupId>
                <artifactId>maven-compiler-pluginartifactId>
                <version>3.1version>
                <configuration>
                    <source>1.8source>
                    <target>1.8target>
                    <encoding>UTF-8encoding>
                    
                configuration>
            plugin>

本人人结合Demo 一起实现滴~不一定非得一模一样, 模仿大概即可…

编写Map阶段

(1)编写Mapper实现类用于执行MapTask。

一个Mapper应该继承于Hadoop提供的Mapper类 import org.apache.hadoop.mapreduce.Mapper; 并重写map( )方法。
Mapper 类是一个泛型类型,它有四个形参类型,其类定义关键代码如下:
MyMapper.Java

import org.apache.hadoop.io.*;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
//继承Mapper 接口;
//k1:表示输入键类型。         v1:表示输入值类型。
//k2:表示输出键类型。         v2:表示输出值类型。
public class MyMapper extends Mapper<LongWritable,Text,Text,LongWritable> {
     
    /*
    Split:程序自动拆分阶段...
        k1   v1
        0   hello
        5   world
        10  hadoop

    Map:用户可以自定义处理阶段...继承Mapper接口,实现map(..);方法
        k2      v2
        hello   1
        hadoop  1
        hello   1
     */
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
     
        //打印每个键值数据..
        System.out.println("k1:"+key+"\tv1:"+value);
        //获取一行数据 hello,world,hadoop 
        String line = value.toString();
        //分析,并根据 ,逗号拆分每个单词...
        //注意拆分时候不要有空格, 可能会影响拆分,进而影响结果..
        String[] split = line.split(",");
        //循环操作生成k2  v2,并写入context中交给下面操作..
        for (String str : split) {
     
        				  //写入每个拆分字符k2    每次都是 1
            context.write(new Text(str.trim()),new LongWritable(1));
        }
    }
}

编写Reduce阶段

(2)编写Reducer实现类用于执行ReduceTask.

同样的 一个Reducer应该继承于Hadoop提供的Reducer类 import org.apache.hadoop.mapreduce.Reducer; 并重写reduce( )方法。
Reducer类也是一个泛型类型,它有四个形参类型,其类定义关键代码如下:
MyReducer.Java

import org.apache.hadoop.io.*;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
//继承Reducer 接口;
//把新的k2   v2  ----转换----  k3   v3
public class MyReducer extends Reducer<Text, LongWritable, Text, LongWritable> {
     
    /*
    k2   v2经过 suffer 分区 排序 规约 分组产生新的k2  v2(迭代器~)
        hello     <1,1,..>
        word      <1,1,..>
    reduce对多个新的k2 v2进行汇合..成为k3 v3
    因为一个大文件可能差分出很多个小文件由多个不同Map执行 (Shuffle可以理解Map过程中的一个小汇总),Reduce是对其最后的汇总操作...
    k3      v3
        hello   2
        word    2
     */
    @Override
    protected void reduce(Text key, Iterable<LongWritable> values, Context context) throws IOException, InterruptedException {
     
        System.out.println("k2:" + key + "v2s:" + values);
        //计数器
        long count = 0;
        //循环遍历迭代器,并计数!
        for (LongWritable value : values) {
     
            count += value.get();
        }
        //生成k3  v3
        context.write(key, new LongWritable(count));
    }
}

编写Job执行:

(3) 创建Job, 主要是为Job指定Mapper 和Reducer, 还包括作业配置、作业提交。最终输出最终结果!
MainJob.Java 继承Configured 实现 Tool接口;

package MR_1;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.*;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;	

public class MainJob extends Configured implements Tool {
     
    @Override
    public int run(String[] args) throws Exception {
     
        //创建一个作业: 配置 还有作业名可随意更改~
        Job job = Job.getInstance(super.getConf(), "my_mr_job");
        job.setJarByClass(MainJob.class);
        //设置输入(读取的操作)的类型 TextInputFormat
        job.setInputFormatClass(TextInputFormat.class);
        //设置读取的文件,读取输入文件解析成key,value对
        TextInputFormat.addInputPath(job, new Path("hdfs://192.168.1.110:9000/input/wordcount.txt"));

        //设置指定我们的 map阶段
        job.setMapperClass(MyMapper.class);
        //设置map的输出 k2 和v2的类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(LongWritable.class);

        //设置reducer阶段
        job.setReducerClass(MyReducer.class);
        //设置reducer的输出 k3 和v3的类型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(LongWritable.class);

        //设置输出文件: 可以是本地/文件系统
        //TextOutputFormat.setOutputPath(job, new Path("hdfs://192.168.1.110:9000/wordcount_out"));
        TextOutputFormat.setOutputPath(job, new Path("D:\\mydata\\out"));

        //执行,返回boolean 结果: true执行成功!
        boolean isok = job.waitForCompletion(true);
        System.out.println(isok);
        return  isok?0:1;
    }

    public static void main(String[] args) throws Exception {
     
        Configuration config = new Configuration();
        ToolRunner.run(config, new MainJob(), args);
    }
}

Maven打包部署 / 执行…

方式一: 直接在项目中main方法调用.执行

Hadoop学习 (MapReduce 过程详解及其性能优化_第7张图片
Hadoop学习 (MapReduce 过程详解及其性能优化_第8张图片

方式二:

项目达成 jar包,上传linux 上, 使用Hadoop jar命令执行...
Maven打成jar包
Hadoop学习 (MapReduce 过程详解及其性能优化_第9张图片
WinSCP 上传至linux
执行Hadoop Jar命令:
hadoop jar [运行的上传].jar [jar包下的执行类]
Hadoop学习 (MapReduce 过程详解及其性能优化_第10张图片
执行后HDFS的文件!
Hadoop学习 (MapReduce 过程详解及其性能优化_第11张图片

Shuffle 详解:

Shuffle包含: 分区 排序 规约…接下来详细介绍:

分区:

partition.csv 文件为例子,需要同学可以私聊...或自定义文件来分析实验!
Hadoop学习 (MapReduce 过程详解及其性能优化_第12张图片
解析文件, 针对文件, 以某种格式进行分区… 多个文件;

举例: 一组电话号码中
135开头的手机号分到一个文件里面去,
136开头的手机号分到一个文件里面去,
137开头的手机号分到一个文件里面去,
138开头的手机号分到一个文件里面去,
139开头的手机号分到一个文件里面去,
其他开头的手机号分到一个文件里面去

本次解析 partition.csv文件, 第6列, 大于15一个文件, 小于15一个文件…
MyMapper.Java

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 MyMapper extends Mapper<LongWritable, Text,Text, NullWritable> {
     
    /*
    * Map阶段: 本次并不是解析文件,而目的是拆分文件..
    *   LongWritable    Text    ————转换————     Text    NullWritable
    *   下标~          一行文件                   一行文件      空 
    * */
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
     
        System.out.println("map执行");
        context.write(value, NullWritable.get());
    }
}

MyPart.Java

自定义分区逻辑… 继承Partitioner泛型类

import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;
//继承Partitioner泛型类,Map阶段过来的 k2 v2
//本次: k Text  v Null..
public class MyPart extends Partitioner<Text, NullWritable> {
     
    //重写风区方法,返回值int 根据返回值hadoop给分区的数据,创建对应的文件;
    @Override
    public int getPartition(Text text, NullWritable nullWritable, int i) {
     
        String[] split = text.toString().split("\t");
        int value = Integer.parseInt(split[5]);
        if (value>15){
     
            return 1;       //输出文件:part-r-00001
        }
        return 0;           //输出文件:part-r-00000
    }
}

MyReduce.Java

import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
//并没有什么需要特别注意的... 
//数据经过Shuffle 之后,这里Reduce并不向以前一样汇总... 要根据需求进行操作,本次目的就是根据 大小与15拆分两个文件!
public class MyReduce extends Reducer<Text, NullWritable,Text,NullWritable> {
     
    @Override
    protected void reduce(Text key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
     
        //直接输出返回  Text null..
        context.write(key, NullWritable.get());
    }
}

Mymain.Java 设置分区的配置类型, Reduce的执行数量,根据分区的个数配置

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
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.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;

public class Mymain extends Configured implements Tool {
     
    @Override
    public int run(String[] strings) throws Exception {
     
        //创建一个作业
        Job job = Job.getInstance(super.getConf(), "my_part_job");
        job.setJarByClass(Mymain.class);
        //设置输入的类型
        job.setInputFormatClass(TextInputFormat.class);
        //设置读取的文件
        TextInputFormat.addInputPath(job, new Path("hdfs://192.168.1.110:9000/input2/partition.csv"));

        //设置map
        job.setMapperClass(MyMapper.class);
        //设置map的输出 k2 和v2的类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(NullWritable.class);

        //设置reducer阶段
        job.setReducerClass(MyReduce.class);
        //设置reducer的输出 k3 和v3的类型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(NullWritable.class);

        //设置输出文件
        TextOutputFormat.setOutputPath(job, new Path("D:\\out2"));
        //设置分区的配置类型
        job.setPartitionerClass(MyPart.class);
        //reduce的执行数量,根据分区的个数配置
        job.setNumReduceTasks(2);
        //执行
        job.waitForCompletion(true);
        return 0;
    }
    public static void main(String[] args) throws Exception {
     
        Configuration config = new Configuration();
        ToolRunner.run(config, new Mymain(), args);
    }
}

排序:

将文件以, 某种特定的形式进行排序
创建一个 abc.txt文件并上传HDFS方便测试:
abc.txt

a 1 
a 9 
b 3 
a 7 
b 8 
b 10 
a 5 

Hadoop学习 (MapReduce 过程详解及其性能优化_第13张图片

为了方便操作定一个SortBean.Java 类:

import org.apache.hadoop.io.WritableComparable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
//自定义一个用于比较大小的类型, 继承 WritableComparable 泛型类;
//使文件支持可序列化, 重写三个方法();
public class SortBean implements WritableComparable<SortBean> {
     
    //自定义属性
    //表示文件的的一行数据
    private String world;
    //当前文件的排序大小~
    private Integer num;

    //实际内部比较大小的方法, 自定义重写...
   @Override
    public int compareTo(SortBean o) {
     
        //获取当前文本的大小~ this.world.compareTo(); 是系统本身存在的一个比较器的方法(); 
        //将泛型类传进来的对象的 world属性, 与自身 对象.world 作比较!结果一样返回 0;
        int result = this.world.compareTo(o.world);
        //判断文本是否一样,如果一样则继续比较他们的 num 属性;
        if (result == 0) {
     
            return this.num - o.num;  //当前num - 传入对象的num,+数升序排列 -负数反之~
        }
        return result;
    }
    //因为MapReduce操作需要满足网络之间的传输...所以需要:序列化操作
    @Override
    public void write(DataOutput dataOutput) throws IOException {
     
        dataOutput.writeUTF(world);
        dataOutput.writeInt(num);
    }
    //反序列化操作;
    @Override
    public void readFields(DataInput dataInput) throws IOException {
     
        this.world = dataInput.readUTF();
        this.num = dataInput.readInt();
    }
    //重写toString(); 使输出对象展示形式... word + num (文本 + 排序大小)
    @Override
    public String toString() {
     
        return "SortBean{" +
                "world='" + world + '\'' +
                ", num=" + num +
                '}';
    }
    //get/set方法	
    public String getWorld() {
     
        return world;
    }
    public void setWorld(String world) {
     
        this.world = world;
    }
    public Integer getNum() {
     
        return num;
    }
    public void setNum(Integer num) {
     
        this.num = num;
    }
}

MySortMapper.Java

import org.apache.hadoop.io.*;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
public class MySortMapper extends Mapper<LongWritable, Text, SortBean, NullWritable> {
     
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
     
        //获取一行文本eg: a 1 空格分隔 " "
        String[] s = value.toString().split(" ");
        //创建一个比较大小 sortBean 对象;  并赋值;
        SortBean sortBean = new SortBean();
        sortBean.setWorld(s[0]);
        sortBean.setNum(Integer.parseInt(s[1]));
        //继续传递~直到经过Shuffle 排序...
        context.write(sortBean, NullWritable.get());
    }
}

MySortReduce.Java

import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
//这里并没有什么特殊的...
public class MySortReduce extends Reducer<SortBean, NullWritable, SortBean, NullWritable> {
     
    @Override
    protected void reduce(SortBean key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
     
        context.write(key, NullWritable.get());
    }
}

MainJob.java

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
public class MainJob extends Configured implements Tool {
     
    @Override
    public int run(String[] args) throws Exception {
     
        //创建一个作业
        Job job = Job.getInstance(super.getConf(), "my_sort_job");
        job.setJarByClass(MainJob.class);
        //设置输入的类型
        job.setInputFormatClass(TextInputFormat.class);
        //设置读取的文件
        TextInputFormat.addInputPath(job, new Path("hdfs://192.168.1.110:9000/input2/abc.txt"));
        
        //设置map
        job.setMapperClass(MySortMapper.class);
        //设置map的输出 k2 和v2的类型
        job.setMapOutputKeyClass(SortBean.class);
        job.setMapOutputValueClass(NullWritable.class);
        
        //设置reducer阶段
        job.setReducerClass(MySortReduce.class);
        //设置reducer的输出 k3 和v3的类型
        job.setOutputKeyClass(SortBean.class);
        job.setOutputValueClass(NullWritable.class);

        //设置输出文件
        TextOutputFormat.setOutputPath(job, new Path("D:\\out3"));
        //虽然这里并没有指定..排序的一些属性,Hadoop Shuffle底层会根据 k2的类型发现继承了WritableComparable 默认进行一系列操作..
        job.waitForCompletion(true);
        return 0;
    }

    public static void main(String[] args) throws Exception {
     
        Configuration config = new Configuration();
        ToolRunner.run(config, new MainJob(), args);
    }
}

Hadoop 执行到Shuffle排序时候, 底层会根据
k2的类型 继承了WritableComparable 而默认进行一系列操作…自动的完成排序操作~

规约:

Hadoop 核心作用就是分析数据, 海量数据中精确计算出某一个字段出现的次数… 这个都是 Reduce完成的l
当然, 对Reduce的压力太大了!
规约就是, 在还没有到Redeuce阶段, Shuffle阶段时候就对Map阶段的数据进行一次小的汇总!以减轻网络之间传输时候的压力

Hadoop学习 (MapReduce 过程详解及其性能优化_第14张图片

wordcount.txt 文件举例, 求出在Shuffle阶段对齐进行一次汇总
首先执行一下原程序:
Hadoop学习 (MapReduce 过程详解及其性能优化_第15张图片
此处可以看到一个文件,读取12行 输出9行底层Reduce把相同的数据进行了汇总...
上执行图:
** Shuffle中的Combiner规约:在Map阶段就给其进行了一次汇总的操作...**

实现步骤

  1. 自定义一个 combiner 继承 Reducer,重写 reduce 方法
  2. 在 job 中设置 job.setCombinerClass(CustomCombiner.class)
    combiner 能够应用的前提是不能影响最终的业务逻辑,而且,combiner 的输出 kv 应该跟reducer 的输入 kv 类型要对应起来;

上述实例1代码并无更改:MyCombiner.Java

import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
/**
 * mapper完了之后在 自身做了一次合并.并非是最终的reducer的操作
 */
public class MyCombiner extends Reducer<Text, LongWritable, Text, LongWritable> {
     

    @Override
    protected void reduce(Text key, Iterable<LongWritable> values, Context context) throws IOException, InterruptedException {
     
        long count = 0;
        for (LongWritable value : values) {
     
            count += value.get();
        }
        //生成k2  v2
        context.write(key, new LongWritable(count));
    }
}

MainJob.Java

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.*;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
public class MainJob extends Configured implements Tool {
     
    @Override
    public int run(String[] args) throws Exception {
     
        //创建一个作业
        Job job = Job.getInstance(super.getConf(), "my_mr_job");
        job.setJarByClass(MainJob.class);
        //设置输入的类型
        job.setInputFormatClass(TextInputFormat.class);
        //设置读取的文件
        TextInputFormat.addInputPath(job, new Path("hdfs://192.168.1.110:9000/input/wordcount.txt"));

        //设置map
        job.setMapperClass(MyMapper.class);
        //设置map的输出 k2 和v2的类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(LongWritable.class);

        //设置reducer阶段
        job.setReducerClass(MyReducer.class);
        //设置reducer的输出 k3 和v3的类型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(LongWritable.class);

        //设置输出文件
        TextOutputFormat.setOutputPath(job, new Path("D:\\out"));
        
        //设置Shuffle 规约~
        job.setCombinerClass(MyCombiner.class);
        //执行
        job.waitForCompletion(true);
        return 0;
    }
    public static void main(String[] args) throws Exception {
     
        Configuration config = new Configuration();
        ToolRunner.run(config, new MainJob(), args);
    }
}

结果
Hadoop学习 (MapReduce 过程详解及其性能优化_第16张图片

总结:

这就是Shuffle 的组成部分~ 分组是Shuffle的底层自己完成的..就不深入了
学习需要灵活,变通上面的例子都是一个个的小例子, 真正实际开发中一定不上这个样子,…
需要根据情况处理更复杂的业务需求... 更复杂的参数…加油!


this 是本人的学习整理的笔记, 如有漏洞出处的地方还请提示指出~
ok, 终于搞定了

你可能感兴趣的:(大数据,编程语言,hadoop,mapreduce,java)