IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> 04大数据技术之Hadoop(MapReduce) -> 正文阅读

[大数据]04大数据技术之Hadoop(MapReduce)

04大数据技术之Hadoop(MapReduce)(老师:尚硅谷)

第1章 MapReduce概述

1.1 MapReduce定义

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

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

1.2 MapReduce优缺点

1.2.1 优点

  • MapReduce 易于编程

    简单的实现一些接口,就可以完成一个分布式程序。

  • 良好的扩展性

    通过简单的增加机器即可扩展计算能力。

  • 高容错性

    MapReduce 设计的初衷就是使程序能够部署在廉价的 PC 机器上,这就要求它具有很高的容错性。比如其中一台机器挂了,它可以把上面的计算任务转移到另外一个节点上运行,不至于这个任务运行失败,而且这个过程不需要人工参与,而完全是由 Hadoop 内部完成的。

  • 适合PB级以上海量数据的离线处理

    可以实现上千台服务器集群并发工作,提供数据处理能力。

1.2.2 缺点

  • 不擅长实时计算

    MapReduce 无法像 MySQL 一样,在毫秒或者秒级内返回结果。

  • 不擅长流式计算

    流式计算的输入数据是动态的,而 MapReduce 的输入数据集是静态的,不能动态变化。

  • 不擅长DAG(有向五环图)计算

    每个 MapReduce 作业的输出结果都会写入到磁盘,会造成大量的磁盘 IO,导致性能非常的低下。

1.3 MapReduce核心思想

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

1.4 MapReduce进程

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

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

1.5 官方WordCount源码

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

1.6 常用数据序列化类型

Java类型Hadoop Writable类型
BooleanBooleanWritable
ByteByteWritable
IntIntWritable
FloatFloatWritable
LongLongWritable
DoubleDoubleWritable
StringText
MapMapWritable
ArrayArrayWritable
NullNullWritable

1.7 MapReduce编程规范

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

  1. Mapper阶段

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

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

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

1.8 WordCount案例实操

1.8.1 本地测试

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

  • 环境准备

    创建Maven工程,MapReduceDemo

    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>
    

    在项目的 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
    

    创建包名:com.atguigu.mapreduce.wordcount

    在这里插入图片描述

    在这里插入图片描述

    WordCountMapperpublic class WordCountMapper extends Mapper<LongWritable, Text,Text, IntWritable> {
        Text k = new Text();
        IntWritable v = new IntWritable(1);
    
        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException,InterruptedException{
            // 1. 获取1行
            String line = value.toString();
            // 2. 切割
            String[] words = line.split(" ");
            //3. 输出
            for (String word:words){
                k.set(word);
                context.write(k,v);
            }
        }
    }
    
    WordCountReducerpublic class WordCountReducer extends Reducer<Text,IntWritable,Text,IntWritable> {
        int sum;
        IntWritable v = new IntWritable();
    
        @Override
        protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
            //1. 累加求和
            sum = 0;
            for (IntWritable count : values){
                sum += count.get();
            }
    
            // 2. 输出
            v.set(sum);
            context.write(key,v);
        }
    }
    
    WordCountDriver类
    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. 关联本Driver程序的jar
            job.setJarByClass(WordCountDriver.class);
    
            // 3. 关联mapper输出的kv类型
            job.setMapperClass(WordCountMapper.class);
            job.setReducerClass(WordCountReducer.class);
    
            // 4. 设置mapper输出的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("D:\\input\\inputword"));
            FileOutputFormat.setOutputPath(job,new Path("D:\\hadoop\\output1"));
    
            // 7. 提交job
            boolean result = job.waitForCompletion(true);
            System.exit(result ? 0 : 1);
    
        }
    }
    

    【注意:输出目录不能创建完全,否则报错】

1.8.2 提交到集群测试

  1. 添加打包插件

  2. 将程序打成jar包

    在这里插入图片描述

  3. 这里可以更改一下输入输出路径为命令行参数

  4. 传至虚拟机

    在这里插入图片描述

  5. 运行

    查看集群上已有数据

    在这里插入图片描述

    这里记得先启动Hadoop103的start-yarn.sh

    在这里插入图片描述

    执行完毕

    在这里插入图片描述

第2章 Hadoop序列化

2.1 序列化概述

  • 什么是序列化

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

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

  • 为什么要序列化

    序列化可以存储“活的”对象,可以将“活的”对象发送到远程计算机。

  • 为什么不用Java的序列化

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

  • Hadoop序列化特点

    • 紧凑:高效实用存储空间
    • 快速:读写数据的额外开销小
    • 互操作:支持多语言的交互

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

具体实现步骤:

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

2.3 序列化案例实操

FlowBean.java

// 1. 继承writable接口
public class FlowBean implements Writable {

    private long upFlow; // 上行流量
    private long downFlow; //下行流量
    private long sumFlow; // 总流量
    // 2. 提供无参构造
    public FlowBean(){
    }
    //3. 提供三个参数的getter和setter方法
    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() {
        this.sumFlow = this.upFlow + this.downFlow;
    }
    // 4. 实现序列化和反序列化方法,注意顺序一定要保持一致

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

    @Override
    public void readFields(DataInput dataInput) throws IOException {
        this.upFlow = dataInput.readLong();
        this.downFlow = dataInput.readLong();
        this.sumFlow = dataInput.readLong();
    }
    //重写ToString

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

FlowMapper.java

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 获取一行数据,转成字符串
        String line = value.toString();
        //2 切割数据
        String[] split = line.split("\t");
        //抓取需要的数据
        String phone = split[1];
        String up = split[split.length - 3];
        String down = split[split.length - 2];

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

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

FlowReducer.java

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 {

        long totalUp = 0;
        long totalDown = 0;
        //1. 遍历values,将上下行分别累加
        for (FlowBean flowBean : values){
            totalUp += flowBean.getUpFlow();
            totalDown += flowBean.getDownFlow();
        }

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

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

    }
}

FlowDriver.java

public class FlowDriver {
    public static void main(String[] args) throws IOException,ClassNotFoundException,InterruptedException {
        //1. 获取job对象
        Configuration configuration = new Configuration();
        Job job = Job.getInstance(configuration);

        //2. 关联本Driver类
        job.setJarByClass(FlowDriver.class);

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

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

        // 5. 设置程序最终输出的KV类型
        job.setOutputKeyClass(Text.class);
        job.setMapOutputValueClass(FlowBean.class);

        // 6. 设置程序的输入输出路径
        FileInputFormat.setInputPaths(job,new Path("D:\\inputflow"));
        FileOutputFormat.setOutputPath(job,new Path("D:\\flowoutput\\output"));

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

    }
}

在这里插入图片描述

第3章 MapReduce框架原理

3.1 InputFormat数据输入

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

**数据块:**Block是HDFS物理上把数据分成一块一块。数据块是HDFS存储数据单位。

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

  • 一个Job的Map阶段并行度由客户端在提交Job时的切片数决定
  • 每一个Split切片分配一个MapTask并行实例处理
  • 默认情况下,切片大小=BlockSize
  • 切片时不考虑数据集整体,而是逐个针对每一个文件单独切片

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

Job提交流程源码详解

waitForCompletion()

submit();

// 1建立连接
	connect();	
		// 1)创建提交Job的代理
		new Cluster(getConfiguration());
			// (1)判断是本地运行环境还是yarn集群运行环境
			initialize(jobTrackAddr, conf); 

// 2 提交job
submitter.submitJobInternal(Job.this, cluster)

	// 1)创建给集群提交数据的Stag路径
	Path jobStagingArea = JobSubmissionFiles.getStagingDir(cluster, conf);

	// 2)获取jobid ,并创建Job路径
	JobID jobId = submitClient.getNewJobID();

	// 3)拷贝jar包到集群
copyAndConfigureFiles(job, submitJobDir);	
	rUploader.uploadFiles(job, jobSubmitDir);

	// 4)计算切片,生成切片规划文件
writeSplits(job, submitJobDir);
		maps = writeNewSplits(job, jobSubmitDir);
		input.getSplits(job);

	// 5)向Stag路径写XML配置文件
writeConf(conf, submitJobFile);
	conf.writeXml(out);

	// 6)提交Job,返回提交状态
status = submitClient.submitJob(jobId, submitJobDir.toString(), job.getCredentials());

FileInputFormat切片源码解析

  1. 程序先找到数据存储的目录
  2. 开始遍历处理(规划切片)目录下的每一个文件
  3. 遍历第一个文件ss.txt
    1. 获取文件大小fs.sizeOf(ss.txt)
    2. 计算切片大小
    3. 默认情况下,切片大小=blocksize
    4. 开始切,形成第1个切片:ss.txt—0:128M 第2个切片ss.txt—128:256M 第3个切片ss.txt—256M:300M
    5. 将切片信息写到一个切片规划文件中
    6. 整个切片的核心过程在getSplit()方法中完成
    7. InputSplit只记录了切片的元数据信息,比如起始位置、长度以及所在的节点列表等
  4. 提交切片规划文件到YARN上,YARN上的MrAppMaster就可以根据切片规划文件计算开启MapTask个数。

3.1.3 FileInputFormat 切片机制

  • FileInputFormat 切片机制
    • 简单地按照文件的内容长度进行切片
    • 切片大小,默认等于Block大小
    • 切片时不考虑数据集整体,而是逐个针对每一个文件单独切片
  • FileInputFormat切片大小的参数配置
    • 源码中计算切片大小的公式
    • 切片大小设置
    • 获取切片信息API

3.1.4 TextInputFormat

FileInputFormat 实现类

FileInputFormat 常见的接口实现类包括:TextInputFormat、KeyValueTextInputFormat、
NLineInputFormat、CombineTextInputFormat 和自定义 InputFormat 等。

TextInputFormat

TextInputFormat是默认的FileInputFormat实现类。按行读取每条记录。键是存储该行在整个文件中起始字节偏移量,LongWritable类型。值是这行的内容,不包括任何行终止符(换行符和回车符),Text类型。

3.1.5 CombineTextInputFormat 切片机制

框架默认的 TextInputFormat 切片机制是对任务按文件规划切片,不管文件多小,都会是一个单独的切片,都会交给一个 MapTask,这样如果有大量小文件,就会产生大量的MapTask,处理效率极其低下。

应用机制:

CombineTextInputFormat 用于小文件过多的场景,它可以将多个小文件从逻辑上规划到一个切片中,这样,多个小文件就可以交给一个 MapTask 处理。

虚拟存储切片最大值设置:

CombineTextInputFormat.setMaxInputSplitSize(job, 4194304);// 4m
注意:虚拟存储切片最大值设置最好根据实际的小文件大小情况来设置具体的值。

切片机制:

生成切片过程:虚拟存储过程 + 切片过程

3.2 MapReduce工作流程

具体shuffle过程

  1. MapTask收集map()方法输出的kv对,放到内存缓冲区中
  2. 从内存缓冲区不断溢出本地磁盘文件,可能会溢出多个文件
  3. 多个溢出文件会被合并成大的溢出文件
  4. 在溢出过程及合并的过程中,都要调用Partitioner进行分区和针对key进行排序
  5. ReduceTask根据自己的分区号,去各个MapTask机器上取相应的结果分区数据
  6. ReduceTask会抓取到同一个分区的来自不同MapTask的结果文件,ReduceTask会将这些文件再进行合并(归并排序)
  7. 合并成大文件后,Shuffle的过程也就结束了,后面进入ReduceTask的逻辑运算过程(从文件中取出一个一个的键值对Group,调用用户自定义的reduce()方法)

3.3 Shuffle机制

3.3.1 Shuffle机制

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

3.3.2 Partition分区

默 认分区是根据key的hashCode对ReduceTasks个数取模得到的。用户没法控制哪个key存储到哪个分区。

→ 自定义Partitioner

步骤

  1. 自定义类继承Partitioner,重写getPartition()方法
  2. 在Job驱动中,设置自定义Partitioner
  3. 自定义Partition后,要根据自定义Partitioner的逻辑设置相应数量的ReduceTask

【分区总结】

  • 如果ReduceTask的数量> getPartition的结果数,则会多产生几个空的输出文件part-r-000xx;
  • 如果 1 < ReduceTask的数量 < getPartition的结果数,则有一部分分区数据无处存放,会异常
  • 如 果ReduceTask的数量=1,则不管MapTask端输出多少个分区文件,最终结果都交给这一个
    ReduceTask,最终也就只会产生一个结果文件 part-r-00000;
  • 分区号必须从零开始,逐一累加

3.3.4 WritableComparable排序

排序是MapReduce框架中最重要的操作之一。

MapTask和ReduceTask均会对数据按 照key进行排序。该操作属于Hadoop的默认行为。默认排序是按照字典顺序排序,且实现该排序的方法是快速排序。

  • 对于MapTask,它会将处理的结果暂时放到环形缓冲区中,当环形缓冲区使用率达到一定阈值后,再对缓冲区中的数据进行一次快速排序,并将这些有序数据溢写到磁盘上,而当数据处理完毕后,它会对磁盘上所有文件进行归并排序。
  • 对于ReduceTask,它从每个MapTask上远程拷贝相应的数据文件,如果文件大小超过一定阈值,则溢写磁盘上,否则存储在内存中。如果磁盘上文件数目达到一定阈值,则进行一次归并排序以生成一个更大文件;如果内存中文件大小或者数目超过一定阈值,则进行一次合并后将数据溢写到磁盘上。当所有数据拷贝完毕后,ReduceTask统一对内存和磁盘上的所有数据进行一次归并排序。

【排序分类】

  • 部分排序

    MapReduce根据输入记录的键对数据集排序。保证输出的每个文件内部有序。

  • 全排序

    最终输出结果只有一个文件,且文件内部有序。实现方式是只设置一个ReduceTask。

  • 辅助排序

    在Reduce端对key进行分组。

  • 二次排序

    在自定义排序过程中,如果compareTo中的判断条件为两个即为二次排序。

【自定义排序WritableComparable原理分析】

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

3.3.7 Combiner合并

  1. Combiner是MR程序中Mapper和Reducer之外的一种组件。
  2. Combiner组件的父类就是Reducer。
  3. Combiner和Reducer的区别在于运行的位置
    • Combiner是在每一个MapTask所在的节点运行;
    • Reducer是接收全局所有Mapper的输出结果;
  4. Combiner的意义就是对每一个MapTask的输出进行局部汇总,以减小网络传输量。
  5. Combiner能够应用的前提是不能影响最终的业务逻辑,而且,Combiner的输出kv应该跟Reducer的输入kv类型要对应起来。

【自定义Combiner实现步骤】

  1. 自定义一个 Combiner 继承 Reducer,重写 Reduce 方法

  2. 在 Job 驱动类中设置:

    job.setCombinerClass(WordCountCombiner.class);
    

3.4 OutputFormat 数据输出

3.4.1 OutputFormat接口实现类

OutputFormat是MapReduce输出的基类,所有实现MapReduce输出都实现了 OutputFormat接口。

常见的OutputFormat实现类

  • OutoutFormat实现类
  • 默认输出格式TextOutputFormat
  • 自定义OutputFormat

3.5 MapReduce内核源码解析

3.5.1 MapTask 工作机制

  • Read 阶段:MapTask 通过 InputFormat 获得的 RecordReader,从输入 InputSplit 中解析出一个个 key/value。
  • Map 阶段:该节点主要是将解析出的 key/value 交给用户编写 map()函数处理,并产生一系列新的 key/value。
  • Collect 收集阶段:在用户编写 map()函数中,当数据处理完成后,一般会调用OutputCollector.collect()输出结果。在该函数内部,它会将生成的 key/value 分区(调用Partitioner),并写入一个环形内存缓冲区中。
  • Spill阶段:即“溢写”,当环形缓冲区满后,MapReduce会将数据写到本地磁盘上,生成一个临时文件。需要注意的是,将数据写入本地磁盘之前,先要对数据进行一次本地排序,并在必要时对数据进行合并、压缩等操作。
  • Merge 阶段:当所有数据处理完成后,MapTask 对所有临时文件进行一次合并,以确保最终只会生成一个数据文件。

3.5.2 ReduceTask 工作机制

  • Copy 阶段:ReduceTask 从各个 MapTask 上远程拷贝一片数据,并针对某一片数据,如果其大小超过一定阈值,则写到磁盘上,否则直接放到内存中。
  • Sort 阶段:在远程拷贝数据的同时,ReduceTask 启动了两个后台线程对内存和磁盘上的文件进行合并,以防止内存使用过多或磁盘上文件过多。
  • Reduce 阶段:reduce()函数将计算结果写到 HDFS 上。

3.5.3 ReduceTask 并行度决定机制

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

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

【注意事项】

  • ReduceTask=0,表示没有Reduce阶段,输出文件个数和Map个数一致。
  • ReduceTask默认值就是1,所以输出文件个数为一个。
  • 如果数据分布不均匀,就有可能在Reduce阶段产生数据倾斜
  • ReduceTask数量并不是任意设置,还要考虑业务逻辑需求,有些情况下,需要计算全局汇总结果,就只能有1个ReduceTask。
  • 具体多少个ReduceTask,需要根据集群性能而定。
  • 如果分区数不是1,但是ReduceTask为1,是否执行分区过程。答案是:不执行分区过程。因为在MapTask的源码中,执行分区的前提是先判断ReduceNum个数是否大于1。不大于1肯定不执行。

3.6 Join应用

3.6.1 Reduce Join

**Map 端的主要工作:**为来自不同表或文件的 key/value 对,打标签以区别不同来源的记录。然后用连接字段作为 key,其余部分和新加的标志作为 value,最后进行输出。

**Reduce 端的主要工作:**在 Reduce 端以连接字段作为 key 的分组已经完成,我们只需要在每一个分组当中将那些来源于不同文件的记录(在 Map 阶段已经打标志)分开,最后进行合并。

3.6.3 Map Join

Map Join 适用于一张表十分小、一张表很大的场景。

优点:在 Map 端缓存多张表,提前处理业务逻辑,这样增加 Map 端业务,减少 Reduce 端数据的压力,尽可能的减少数据倾斜。

3.7 数据清洗(ETL)

“ETL( Extract-Transform-Load),用来描述将数据从来源端经过抽取(Extract)、转换(Transform)、加载(Load)至目的端的过程。ETL 一词较常用在数据仓库,但其对象并不限于数据仓库。
在运行核心业务 MapReduce 程序之前,往往要先对数据进行清洗,清理掉不符合用户要求的数据。清理的过程往往只需要运行 Mapper 程序,不需要运行 Reduce 程序。

3.8 MapReduce开发总结

  1. 输入数据接口:InputFormat

    • 默认使用的实现类是:TextInputFormat
    • TextInputFormat 的功能逻辑是:一次读一行文本,然后将该行的起始偏移量作为key,行内容作为 value 返回。
    • CombineTextInputFormat 可以把多个小文件合并成一个切片处理,提高处理效率。
  2. 逻辑处理接口:Mapper

    用户根据业务需求实现其中三个方法:map() setup() cleanup ()

  3. Partitioner 分区

    • 有默认实现 HashPartitioner,逻辑是根据 key 的哈希值和 numReduces 来返回一个分区号;key.hashCode()&Integer.MAXVALUE % numReduces
    • 如果业务上有特别的需求,可以自定义分区。
  4. Comparable 排序

    • 当我们用自定义的对象作为 key 来输出时,就必须要实现 WritableComparable 接口,重写其中的 compareTo()方法。
    • 部分排序:对最终输出的每一个文件进行内部排序。
    • 全排序:对所有数据进行排序,通常只有一个 Reduce。
    • 二次排序:排序的条件有两个。
  5. Combiner 合并

    Combiner 合并可以提高程序执行效率,减少 IO 传输。但是使用时必须不能影响原有的业务处理结果。

  6. 逻辑处理接口:Reducer

    用户根据业务需求实现其中三个方法:reduce() setup() cleanup ()

  7. 输出数据接口:OutputFormat

    • 默认实现类是 TextOutputFormat,功能逻辑是:将每一个 KV 对,向目标文本文件输出一行。
    • 可以自定义 OutputFormat

第4章 Hadoop数据压缩

4.1 概述

  • 压缩的优缺点
    • 优点:以减少磁盘 IO、减少磁盘存储空间。
    • 缺点:增加 CPU 开销。
  • 压缩原则
    • 运算密集型的 Job,少用压缩
    • IO 密集型的 Job,多用压缩

4.2 MR支持的压缩编码

压缩算法对比

压缩格式Hadoop自带?算法文件扩展名是否可切片换成压缩格式后,原来的程序是否需要修改
DEFLATEDEFLATE.deflate不需要
GzipDEFLATE.gz不需要
bzip2bzip2.bz2不需要
LZ0LZ0.lzo需要建索引,还需要指定输入格式
SnappySnappy.snappy不需要

压缩性能比较

压缩算法原始文件大小压缩文件大小压缩速度解压速度
gzip8.3GB1.8GB17.5MB/s58MB/s
bzip8.3GB1.1GB2.4MB/s9.5MB/s
LZ08.3GB2.9GB49.3MB/s74.6MB/s

4.3 压缩方式选择

压缩方式选择时重点考虑:压缩/解压缩速度、压缩率(压缩后存储大小)、压缩后是否可以支持切片。

4.3.1 Gzip压缩

优点:压缩率比较高;
缺点:不支持 Split;压缩/解压速度一般;

4.3.2 Bzip2 压缩

优点:压缩率高;支持 Split;
缺点:压缩/解压速度慢。

4.3.3 Lzo 压缩

优点:压缩/解压速度比较快;支持 Split;
缺点:压缩率一般;想支持切片需要额外创建索引。

4.3.4 Snappy 压缩

优点:压缩和解压缩速度快;
缺点:不支持 Split;压缩率一般;

4.3.5 压缩位置选择

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

4.4 压缩参数配置

压缩格式对应的编码/解码器
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

要在Hadoop中启用压缩,配置

参数默认值阶段建议
io.compression.codecs
(在 core-site.xml 中配置)
无,这个需要在命令行输入
hadoop checknative 查看
输入压缩Hadoop 使用文件扩展
名判断是否支持某种
编解码器
mapreduce.map.output.compr
ess(在 mapred-site.xml 中
配置)falsemapper 输出这个参数设为 true 启
用压缩
mapreduce.map.output.compr
ess.codec(在 mapred-site.xml 中配置)
org.apache.hadoop.io.com
press.DefaultCodec
mapper 输出企业多使用 LZO 或
Snappy 编解码器在此
阶段压缩数据
mapreduce.output.fileoutpu
tformat.compress(在
mapred-site.xml 中配置)falsereducer 输出这个参数设为 true 启
用压缩
mapreduce.output.fileoutpu
tformat.compress.codec(在
mapred-site.xml 中配置)org.apache.hadoop.io.com
press.DefaultCodec
reducer 输出使用标准工具或者编
解码器,如 gzip 和
bzip2

第5章 常见错误及解决方案

  • 导包容易出错。尤其 Text 和 CombineTextInputFormat。

  • Mapper 中第一个输入的参数必须是 LongWritable 或者 NullWritable,不可以是 IntWritable. 报的错误是类型转换异常。

  • java.lang.Exception: java.io.IOException: Illegal partition for 13926435656 (4),说明 Partition和 ReduceTask 个数没对上,调整 ReduceTask 个数。

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

  • 缓存 pd.txt 小文件案例中,报找不到 pd.txt 文件
    原因:大部分为路径书写错误。还有就是要检查 pd.txt.txt 的问题。还有个别电脑写相对路径找不到 pd.txt,可以修改为绝对路径。

  • 报类型转换异常。
    通常都是在驱动函数中设置 Map 输出和最终输出时编写错误。Map 输出的 key 如果没有排序,也会报类型转换异常。

  • 集群中运行 wc.jar 时出现了无法获得输入文件。
    原因:WordCount 案例的输入文件不能放用 HDFS 集群的根目录。

  • 自定义Outputformat时,注意在RecordWirter中的close方法必须关闭流资源。否则输出的文件内容中数据为空。

  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2022-07-20 18:56:33  更:2022-07-20 18:57:32 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/20 2:54:22-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码