Hadoop-MapReduce

文章目录

  • 一、MapReduce概述
    • 1. MapReduce定义
    • 2. MapReduce优缺点
      • 1)优点
      • 2)缺点
    • 3. 核心思想
    • 4. MapReduce进程
    • 5. 常用数据序列化类型
    • 6. MapReduce编程规范
      • 1)Mapper
      • 2)Reducer
      • 3)Driver
      • 4)上传jar包到服务器运行
  • 二、Hadoop序列化
    • 1. 序列化概述
    • 2. 自定义bean对象实现序列化接口(Writable)
    • 3. 案例
      • 1)需求说明
      • 2)代码实现
  • 三、MapReduce工作流程
  • 四、InputFormat数据输入
    • 1. FileInputFormat
      • 1)切片与MapTask并行度决定机制
      • 2)FileInputFormat切片机制
        • a. 切片机制
        • b. 案例分析
      • 3)FileInputFormat 实现类
    • 2. TextInputFormat
    • 3. CombineTextInputFormat
      • 1)使用场景
      • 2)虚拟存储切片最大值设置
      • 3)切片机制
        • a. 虚拟存储过程
        • b. 切片过程
        • c. 举例说明
  • 四、Shuffle机制
    • 1. Shuffle工作流程
    • 2. Partition分区
    • 3. 自定义Partitioner
    • 4. 分区总结
    • 5. 案例:将统计结果按照手机归属地不同省份输出到不同文件中
    • 6. WritableComparable 排序
    • 7. Combiner合并
  • 五、OutputFormat数据输出
    • 1. OutputFormat接口实现类
    • 2. 自定义OutputFormat
    • 3. 案例
  • 六、Join应用
    • 1. Reduce Join
    • 2. Map Join
  • 七、Hadoop数据压缩
    • 1. 概述
    • 2. MR支持的压缩编码
      • 1)压缩算法对比介绍
      • 2)压缩性能的比较
    • 3. 压缩方式选择
    • 4. 压缩位置选择
    • 5. 压缩参数配置
    • 6. 案例
      • 1)Map输出端采用压缩
      • 2)Reduce输出端采用压缩
  • 八、MapReduce生产经验
    • 1 使用 Sort 程序测试 MapReduce 计算性能
    • 2. MapReduce常用调优参数
      • 1)Map阶段
      • 2)Reduce阶段
    • 3. MapReduce数据倾斜问题
  • 九、Hadoop小文件优化方法
    • 1. Hadoop小文件弊端
    • 2. Hadoop小文件解决方案
      • 1)在数据采集的时候,就将小文件或小批数据合成大文件再上传HDFS(数据源头)
      • 2)Hadoop Archive(存储方向)
      • 3)CombineTextInputFormat(计算方向)
      • 4)开启uber模式,实现JVM重用(计算方向)

一、MapReduce概述

1. MapReduce定义

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

2. MapReduce优缺点

1)优点

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

2)缺点

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

3. 核心思想

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

WordCount 样例中数据流走向深入理解 MapReduce 核心思想:

Hadoop-MapReduce_第1张图片

4. MapReduce进程

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

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

5. 常用数据序列化类型

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

6. MapReduce编程规范

用户编写的程序分成三个部分:MapperReducerDriver。以官方的 WordCount 为例

WordCount 官方案例源码地址,除了 WordCount 之外,还有其它的样例代码:

1)Mapper

  1. 用户自定义的 Mapper 要继承自己的父类
  2. Mapper 的输入数据是 KV 对的形式(KV的类型可自定义)
  3. Mapper 中的业务逻辑写在map0方法中
  4. Mapper 的输出数据是 KV 对的形式(KV的类型可自定义)
  5. map() 方法(MapTask进程)对每一个 调用一次
/**
 * Mapper, 泛型表示两对kv,依次表示:输入kv,输出kv的类型,假设有如下文本文件:
 * ab cd
 * d ee ab
 * 输入的k表示偏移量,第一行k=0,第二行k=6("ab cd\n"共6个字符)
 * 输入的v表示一行数据,第一行是ab cd,第二行v=ab cd
 * 输出的k表示分词后的字符串,ab/cd/d/ee,按照空格换行等符号进行分隔
 * 输出的v表示相应的字符串的个数,这里是固定值1,
 * 第一行输出:{ab: 1, cd: 1}
 * 第二行输出:{d: 1, ee: 1, ab: 1}
 */
public static class TokenizerMapper
        extends Mapper<Object, Text, Text, IntWritable> {

	//防止多次创建
    private final static IntWritable one = new IntWritable(1);
    private Text word = new Text();

    @Override
    public void map(Object key, Text value, Context context
    ) throws IOException, InterruptedException {
        //将一行字符串分词,按照" \t\n\r\f" :空格字符、制表符、换行符、回车符和换页符分词
        StringTokenizer itr = new StringTokenizer(value.toString());
        while (itr.hasMoreTokens()) {
        	//将String转成Text
            word.set(itr.nextToken());
            context.write(word, one);
        }
    }
}

2)Reducer

  1. 用户自定义的 Reducer 要继承自己的父类
  2. Reducer 的输入数据类型对应 Mapper 的输出数据类型,也是 KV
  3. Reducer 的业务逻辑写在 reduce() 方法中
  4. ReduceTask 进程对每一组相同 k 的 组调用一 次 reduce() 方法
/**
 * Reducer,泛型表示两对kv,依次表示:输入kv,输出kv的类型
 * Mapper输出后的每个 key 只会在 Reducer 中处理一次
 * 所以这里从Mapper中传过来的数为:
 * {ab: [1,1], cd: [1], d: [1], ee: [1]}
 * 合并后输出:{ab: 2 cd: 1, d: 1, ee: 1},实现统计单词逻辑
 */
public static class IntSumReducer
        extends Reducer<Text, IntWritable, Text, IntWritable> {
    private IntWritable result = new IntWritable();

    @Override
    public void reduce(Text key, Iterable<IntWritable> values,
                       Context context
    ) throws IOException, InterruptedException {
        int sum = 0;
        for (IntWritable val : values) {
            sum += val.get();
        }
        result.set(sum);
        context.write(key, result);
    }
}

注意:Hadoop 迭代器中使用了对象重用,即 Reducer 提供的 value 迭代器 Iterable values 迭代时 value 始终指向一个内存地址(引用值始终不变,即同一个对象),改变的是引用指向的内存地址中的数据。所以如果要获取对象,则要新 new 一个对象,然后使用 BeanUtils.copyProperties(a, b) 将参数复制到新对象中

3)Driver

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

/**
 * Driver
 */
public static void main(String[] args) throws Exception {
    Configuration conf = new Configuration();
    String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();
    if (otherArgs.length < 2) {
        System.err.println("Usage: wordcount  [...] ");
        System.exit(2);
    }
    //1. 获取Job
    Job job = Job.getInstance(conf, "word count");
    //2. 设置jar包路径
    job.setJarByClass(WordCount.class);
    //3. 关联Mapper和Reducer
    job.setMapperClass(TokenizerMapper.class);
    job.setCombinerClass(IntSumReducer.class);
    job.setReducerClass(IntSumReducer.class);
    //4. 设置Mapper输出的KV类型,如果和最终输出的KV类型一致,可以不设置
    // job.setMapOutputKeyClass(Text.class);
    // job.setMapOutputValueClass(IntWritable.class);
    //5. 设置最终输出的KV类型
    job.setOutputKeyClass(Text.class);
    job.setOutputValueClass(IntWritable.class);
    //6. 设置输入路径
    for (int i = 0; i < otherArgs.length - 1; ++i) {
        FileInputFormat.addInputPath(job, new Path(otherArgs[i]));
    }
    //7. 设置输出路径
    FileOutputFormat.setOutputPath(job,
            new Path(otherArgs[otherArgs.length - 1]));
    //8. 提交Job
    System.exit(job.waitForCompletion(true) ? 0 : 1);
}

4)上传jar包到服务器运行

pom 文件集成打包插件

<dependencies>
    <dependency>
        <groupId>org.apache.hadoopgroupId>
        <artifactId>hadoop-clientartifactId>
        <version>3.3.1version>
    dependency>
dependencies>

<build>
    <finalName>wcfinalName>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.pluginsgroupId>
            <artifactId>maven-compiler-pluginartifactId>
            <configuration>
                <source>1.8source>
                <target>1.8target>
                <encoding>UTF-8encoding>
            configuration>
        plugin>

		<plugin>
            <artifactId>maven-assembly-pluginartifactId>
            <configuration>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependenciesdescriptorRef>
                descriptorRefs>
            configuration>
            <executions>
                <execution>
                    <id>make-assemblyid>
                    <phase>packagephase>
                    <goals>
                        <goal>singlegoal>
                    goals>
                execution>
            executions>
        plugin>

    plugins>
build>

将打成的 jar 包重命名为 wc.jar 上传到 hadoop 服务器,使用命令运行 jar 包:

# hadoop jar jar包路径 main方法类路径 输入目录 输出目录
hadoop jar wc.jar com.zyx.mapreduce.WordCount /root/input /output/wc

二、Hadoop序列化

1. 序列化概述

  1. 什么是序列化

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

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

  2. 为什么要序列化

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

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

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

  4. Hadoop序列化特点:

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

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

具体实现bean对象序列化步骤如下7步。

  1. 必须实现 Writable 接口

  2. 反序列化时,需要反射调用空参构造函数,所以必须有空参构造

    public FlowBean() {
    }
    
  3. 重写序列化方法

    @Override
    public void write(DataOutput out) throws IOException {
    	out.writeLong(upFlow);
    	out.writeLong(downFlow);
    	out.writeLong(sumFlow);
    }
    
  4. 重写反序列化方法

    @Override
    public void readFields(DataInput in) throws IOException {
    	upFlow = in.readLong();
    	downFlow = in.readLong();
    	sumFlow = in.readLong();
    }
    
  5. 注意:反序列化的顺序和序列化的顺序完全一致

  6. 要想把结果显示在文件中,需要重写 toString()

  7. 如果需要将自定义的 bean 放在 key 中传输,则还需要实现 Comparable 接口,因为 MapReduce 框中的 Shuffle 过程要求对 key 必须能排序。详见后面排序案例。

    @Override
    public int compareTo(FlowBean o) {
    	// 倒序排列,从大到小
    	return this.sumFlow > o.getSumFlow() ? -1 : 1;
    }
    

3. 案例

1)需求说明

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

  1. 输入数据

    1	13736230513	192.196.100.1	www.atguigu.com	2481	24681	200
    2	13846544121	192.196.100.2		264	0	200
    3 	13956435636	192.196.100.3		132	1512	200
    4 	13966251146	192.168.100.1		240	0	404
    5 	18271575951	192.168.100.2	www.atguigu.com	1527	2106	200
    6 	84188413	192.168.100.3	www.atguigu.com	4116	1432	200
    7 	13590439668	192.168.100.4		1116	954	200
    8 	15910133277	192.168.100.5	www.hao123.com	3156	2936	200
    9 	13729199489	192.168.100.6		240	0	200
    10 	13630577991	192.168.100.7	www.shouhu.com	6960	690	200
    11 	15043685818	192.168.100.8	www.baidu.com	3659	3538	200
    12 	15959002129	192.168.100.9	www.atguigu.com	1938	180	500
    13 	13560439638	192.168.100.10		918	4938	200
    14 	13470253144	192.168.100.11		180	180	200
    15 	13682846555	192.168.100.12	www.qq.com	1938	2910	200
    16 	13992314666	192.168.100.13	www.gaga.com	3008	3720	200
    17 	13509468723	192.168.100.14	www.qinghua.com	7335	110349	404
    18 	18390173782	192.168.100.15	www.sogou.com	9531	2412	200
    19 	13975057813	192.168.100.16	www.baidu.com	11058	48243	200
    20 	13768778790	192.168.100.17		120	120	200
    21 	13568436656	192.168.100.18	www.alibaba.com	2481	24681	200
    22 	13568436656	192.168.100.19		1116	954	200
    
  2. 输入数据格式,数据之间使用制表符("\t")进行分隔:

    7	13560436666	120.196.100.99	1116	954	200
    id	手机号码		网络ip	    上行流量  下行流量 网络状态码
    
  3. 期望输出数据格式

    13560436666	1116	954	2070
    手机号码	 上行流量  下行流量 总流量
    

2)代码实现

import lombok.Data;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.Writable;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

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

public class Flow {
    public static void main(String[] args) throws Exception {
        Configuration configuration = new Configuration();
        Job job = Job.getInstance(configuration, "flow");
        job.setJarByClass(Flow.class);
        job.setMapperClass(FlowMapper.class);
        job.setReducerClass(FlowReducer.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(FlowBean.class);

        FileInputFormat.addInputPath(job, new Path("/Users/mac/IdeaProjects/bigdata/Hadoop/tmp/phone_data.txt"));
        FileOutputFormat.setOutputPath(job, new Path("/Users/mac/IdeaProjects/bigdata/Hadoop/output/flow"));
        System.exit(job.waitForCompletion(true) ? 0 : 1);
    }
}

@Data
class FlowBean implements Writable {

    private long upFlow;
    private long downFlow;
    private long 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 {
        upFlow = in.readLong();
        downFlow = in.readLong();
        sumFlow = in.readLong();
    }
}

class FlowMapper extends Mapper<LongWritable, Text, Text, FlowBean> {

    private Text text = new Text();
    private FlowBean flowBean = new FlowBean();

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        String[] split = value.toString().split("\t");
        flowBean.setUpFlow(Long.parseLong(split[4]));
        flowBean.setDownFlow(Long.parseLong(split[5]));
        flowBean.setSumFlow();
        text.set(split[1]);
        context.write(text, flowBean);
    }
}

class FlowReducer extends Reducer<Text, FlowBean, Text, FlowBean> {

    @Override
    protected void reduce(Text key, Iterable<FlowBean> values, Context context) throws IOException, InterruptedException {
        FlowBean resBean = new FlowBean();
        Iterator<FlowBean> iterator = values.iterator();
        while (iterator.hasNext()) {
            FlowBean flowBean = iterator.next();
            resBean.setUpFlow(resBean.getUpFlow() + flowBean.getUpFlow());
            resBean.setDownFlow(resBean.getDownFlow() + flowBean.getDownFlow());
            resBean.setSumFlow();
        }
        context.write(key, resBean);
    }
}

三、MapReduce工作流程

Read -> Map -> Collect -> Spill(溢写) -> Merge -> Copy -> Sory -> Reduce

1-5:Read 阶段:

  1. 待处理文件:ss.txt,200M
  2. 进行切片,切为二片,分别为:0-128,128-200
  3. 将输出的切片信息传给 Yarn
  4. Mrappmaster 计算出 MapTask 数量,并启动两个 MapTask 分别处理两个切片
  5. MapTask 使用 InputFormat 读入数据

6:Mapper:

  1. Mapper 逻辑处理

7-13:Shuffle 过程

  1. MapTask 输出数据到环形缓冲区(一块内存),缓冲区分为两半,如图所示,
    • 一半存元数据(index 索引:唯一ID,partition 分区:同一个分区的数据最终会进入同一个 ReduceTask,最终同一个分区结果会保存在同一个文件中,几个分区生成几个结果文件, keystart key开始的索引, valstart v 开始的索引,0 - keystart 的数据就是 key 值,keystart 与 valstart 之间的数据就是 val 值)
    • 另一半存写入的
    • 缓冲区默认大小为 100M,当缓冲区内存使用超过 80% 时,进行反向溢写(假设写作文,总共要写100行,0-80行写完时,从100行倒着往前写)
  2. 缓冲区内存使用超过 80%,对每个分区中的数据根据 key 进行排序(内存中进行,快排),排序并不移动真实数据,而是通过指针的方式,改变元数据中 keystart 的 valstart 的值
  3. 将排好序的 溢写到磁盘文件,多次溢写后可能会生成多个文件
  4. 多个溢出文件会被合并成大的溢出文件,过程中使用归并排序,保证最后的文件也是有序的,最终每个分区一个大溢出文件(保存在磁盘)
  5. 合并过程中存在优化等操作,例如两个 可以合并为 保存到溢出文件中
  6. 所有 MapTask 任务完成后(可以设置成部分完成后),启动相应数量的 ReduceTask,并告知 ReduceTask 处理数据范围(数据分区)
  7. ReduceTask 根据自己的分区号,去各个 MapTask 机器上拉取相应的结果分区数据,ReduceTask 会抓取到同一个分区的来自不同 MapTask 的结果文件,ReduceTask 会将这些文件再进行合并(归并排序)

14 - 15:Reducer:

  1. 从文件中取出一个一个的键值对 Group,调用用户自定义的 reduce() 方法
  2. Reducer 处理前还可以进行一次分组排序,使用较少

16:OutputFormat

  1. OutputFormat 写出计算结果

四、InputFormat数据输入

1. FileInputFormat

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

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

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

并行度决定机制:

Hadoop-MapReduce_第2张图片

  1. 一个 JobMap 阶段并行度由客户端在提交 Job 时的切片数决定
  2. 每一个 Split 切片分配一个 MapTask 并行实例处理,最好是切片和 MapTask 在同一个节点上
  3. 默认情况下,切片大小=BlockSize,如果 切片大小 != BlockSize 时,可能会出现文件与 MapTask 不在一台机器的情况
  4. 切片时不考虑数据集整体,而是逐个针对每一个文件单独切片。当传入两个文件时,两个文件单独切片,不会拼接后切片。

2)FileInputFormat切片机制

a. 切片机制

  1. 简单地按照文件的内容长度进行切片

  2. 切片大小,默认等于 Block 大小,可以通过指定最大值和最小值来设置

    切片最小值:mapreduce.input.fileinputformat.split.minsize=1,默认为1
    切片最大值:mapreduce.input.fileinputformat.split.maxsize=Long.MAX_VALUE,默认值为Long.MAX_VALUE
    minSize < blockSize < maxSize 时,切片大小=blockSize

  3. 每次切片时,都要判断切完剩下的部分是否大于块的 1.1 倍,不大于 1.1 倍就划分一块切片

  4. 切片时不考虑数据集整体,而是逐个针对每一个文件单独切片

  5. 切片完成后,将切片信息写到一个切片规划文件中,整个切片的核心过程在 getSplit() 方法中完成,InputSplit 只记录了切片的元数据信息,比如起始位置、长度以及所在的节点列表等。

  6. 最后提交切片规划文件到 YARN 上,YARN 上的 MrAppMaster 就可以根据切片规划文件计算开启 MapTask 个数。

源码位置:org.apache.hadoop.mapreduce.lib.input.FileInputFormat#getSplits(JobContext job)

b. 案例分析

输入数据有两个文件:
file1.txt 	320M
file2.txt	10M
file3.txt	129M

经过 FileInputFormat 的切片机制运算后,形成的切片信息如下:
file1.txt.split1-- 	0~128M
file1.txt.split2-- 	128~256M
file1.txt.split3--	256~320M
file2.txt.split1--	0~10M
file3.txt.split1-- 	0~129M

3)FileInputFormat 实现类

在运行 MapReduce 程序时,输入的文件格式包括:基于行的日志文件、二进制格式文件、数据库表等。因此,针对不同的输入数据类型,需要通过继承 FileInputFormat 父类来支持不同格式的输入文件

FileInputFormat 常见的实现类包括:TextInputFormat(默认)、KeyValueTextInputFormat(处理KV)、NLineInputFormat(处理多行)、CombineTextInputFormat(多个文件合并处理)和自定义InputFormat 等。

2. TextInputFormat

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

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

以下是一个示例,比如,一个分片包含了如下2条文本记录。

Rich learning form
Intelligent learning engine

每条记录表示为以下键/值对(Windows):
(0,Rich learning form\r\n)
(20,Intelligent learning engine)

每条记录表示为以下键/值对(Linux):
(0,Rich learning form\n)
(19,Intelligent learning engine)

3. CombineTextInputFormat

1)使用场景

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

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

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

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

注意:虚拟存储切片最大值设置最好根据实际的小文件大小情况来设置具体的值。

3)切片机制

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

a. 虚拟存储过程

将输入目录下所有文件大小,依次和设置的 setMaxInputSplitSize 值比较,会出现3种情况:

  1. 如果 文件大小 <= 设置的最大值,逻辑上划分一个块。
  2. 如果 设置的最大值 < 文件大小 <= 设置的最大值 * 2:此时将文件均分成 2 个虚拟存储块(防止出现太小切片)。
  3. 如果 设置的最大值 * 2 < 文件大小:那么以设置的最大值切割一块,然后剩下的数据继续进行判断;
    例如 setMaxInputSplitSize 值为4M,输入文件大小为8.02M,则先逻辑上分成一个4M。剩余的大小为4.02M,如果按照4M逻辑划分,就会出现0.02M的小的虚拟存储文件,所以将剩余的4.02M文件切分成(2.01M和2.01M)两个文件。

b. 切片过程

根据虚拟存储的结果进行切片:

  1. 判断虚拟存储的文件大小是否大于 setMaxInputSplitSize 值,大于等于则单独形成一个切片。
  2. 如果不大于则跟下一个虚拟存储文件进行合并,合并后继续判断文件大小,如果小于 setMaxInputSplitSize 值,则继续向后合并,否则生成新的切片

c. 举例说明

Hadoop-MapReduce_第3张图片

四、Shuffle机制

1. Shuffle工作流程

Map 方法之后,Reduce 方法之前的数据处理过程称之为 Shuffle。
Hadoop-MapReduce_第4张图片

2. Partition分区

默认 Partitioner 分区实现逻辑:根据 key 的 hashCode 对 ReduceTasks 个数取模得到的。用户没法控制哪个 key 存储到哪个分区:

public class HashPartitioner<K2, V2> implements Partitioner<K2, V2> {

  public void configure(JobConf job) {}

  /** Use {@link Object#hashCode()} to partition. */
  public int getPartition(K2 key, V2 value,
                          int numReduceTasks) {
    //key.hashCode() & Integer.MAX_VALUE:避免 hashCode 值溢出
    return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;
  }

}

3. 自定义Partitioner

  1. 自定义类继承 Partitioner,重写 getPartition() 方法

    public class FlowPartition extends Partitioner<Text, FlowBean> {
        @Override
        public int getPartition(Text text, FlowBean flowBean, int numPartitions) {
        	//控制分区代码逻辑
        	return partition;
        }
    }
    
  2. 在 Job 驱动中,设置自定义 Partitioner

    job.setPartitionerClass(FlowPartition.class);
    
  3. 自定义 Partition 后,要根据自定义 Partitioner 的逻辑设置相应数量的 ReduceTask

    job.setNumReduceTasks(5);
    

4. 分区总结

  1. numReduceTask > getPartition(),则会多产生几个空的输出文件 part-r-000xx
  2. 1 < numReduceTask < getPartition(),则有一部分分区数据无处安放,会抛 IOException ;
  3. 如果 numReduceTask=1(默认),不会走自定义的 PartitionergetPartition() 始终返回 0,所以不管 MapTask 输出多少个分区文件,最终结果都交给这一个 ReduceTask ,最终也就只会产生一个结果文件 part-00000
  4. 如果 numReduceTask=0,则程序会在 Mapper 之后直接输出,不会有 Shuffle 和 Reducer 阶段,输出文件个数和 Map 个数一致
  5. 分区号必须从零开始,逐一累加。

案例分析:

例如:假设自定义分区数为5 ,则

  1. job.setNumReduceTasks(1):会正常运行,只不过会产生一个输出文件
  2. job.setNumReduceTasks(2):会报错
  3. job.setNumReduceTasks(6):大于5,程序会正常运行,会产生空文件

5. 案例:将统计结果按照手机归属地不同省份输出到不同文件中

样例数据见 2.3.1,手机号136、137、138、139开头的分别放到一个独立的4个文件中,其他开头的放到一个文件中。

在 2.3.1 案例的基础上,做如下修改:

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.Partitioner;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

public class FlowPartition extends Partitioner<Text, FlowBean> {
    public static void main(String[] args) throws Exception {
        Configuration configuration = new Configuration();
        Job job = Job.getInstance(configuration, "flow");
        job.setJarByClass(Flow.class);
        job.setMapperClass(FlowMapper.class);
        job.setReducerClass(FlowReducer.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(FlowBean.class);
        
        //设置自定义Partitioner
        job.setPartitionerClass(FlowPartition.class);
		//设置相应数量的ReduceTask
        job.setNumReduceTasks(5);

        FileInputFormat.addInputPath(job, new Path("/Users/mac/IdeaProjects/bigdata/Hadoop/input/phone_data.txt"));
        FileOutputFormat.setOutputPath(job, new Path("/Users/mac/IdeaProjects/bigdata/Hadoop/output/flowPartition"));
        System.exit(job.waitForCompletion(true) ? 0 : 1);
    }

    @Override
    public int getPartition(Text text, FlowBean flowBean, int numPartitions) {
        String s = text.toString();
        String pre = phone.substring(0, 3);
        switch (pre) {
            case "136":
                return 0;
            case "137":
                return 1;
            case "138":
                return 2;
            case "139":
                return 3;
            default:
                return 4;
        }
    }
}

6. WritableComparable 排序

排序是 MapReduce 框架中最重要的操作之一。MapTaskReduceTask 均会对数据按照 key 进行排序。该操作属于 Hadoop 的默认行为。任何应用程序中的数据均会被排序,而不管逻辑上是否需要。默认排序是按照字典顺序排序,且实现该排序的方法是快速排序。

  • 对于MapTask ,它会将处理的结果暂时放到环形缓冲区中,当环形缓冲区使用率达到一定阈值后,再对缓冲区中的数据进行一次快速排序 ,并将这些有序数据溢写到磁盘上,而当数据处理完毕后,它会对磁盘上所有文件进行归并排序。
  • 对于ReduceTask,它从每个MapTask上远程拷贝相应的数据文件,如果文件大小超过一定阈值,则溢写磁盘上,否则存储在内存中。如果磁盘上文件数目达到一定阈值 ,则进行一次归并排序以生成一个更大文件;如果内存中文件大小或者数目超过一定阈值,则进行一次合并后将数据溢写到磁盘 上。当所有数据拷贝完毕后, ReduceTask 统一对内存和磁盘 上的所有数据进行一次归并排序。
  1. 部分排序:MapReduce 根据输入记录的键对数据集排序。保证输出的每个文件内部有序。
  2. 全排序:最终输出结果只有一个文件,且文件内部有序。实现方式是只设置一个 ReduceTask。 但该方法在处理大型文件时效率极低,因为一台机器处理所有文件,完全丧失了 MapReduce 所提供的并行架构。
  3. 辅助排序(GroupingComparator分组):在 Reduce 端对 key 进行分组。应用于在接收的 key 为 bean 对象时,想让一个或几个字段相同(全部字段比较不相同)的 key 进入到同一个 reduce 方法时,可以采用分组排序。
  4. 二次排序:在自定义排序过程中,如果compare’ To中的判断条件为两个即为_次排序。

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

@Data
class FlowBean implements WritableComparable<FlowBean> {
	private int id;
    @Override
    public void write(DataOutput out) throws IOException {
    }

    @Override
    public void readFields(DataInput in) throws IOException {
    }

    @Override
    public int compareTo(FlowBean o) {
    	//大于0升序排,小于0降序排
        return this.getId() - o.getId();
    }
}

7. Combiner合并

Combiner 是MR程序中 Mapper 和 Reducer 之外的一种组件,Combiner 父类就是 Reducer,两者的区别在于运行的位置:

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

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

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

错误使用案例:
假如有如下两个 Mapper,然后由 Combiner 进行合并
3 5 7 ->(3+5+7)/3=5
2 6 -> (2+6)/2=4
可以看到,经过 Combiner 合并后, Reducer 数据处理出现错误
(3+5+7+2+6)/5=23/5 不等于 (5+4)/2=9/2

自定义一个 Combiner 继承 Reducer,重写 reduce() 方法,就可以实现自定义 Combiner,一般 Combiner 逻辑和 Reducer 逻辑相同,可以共用一个类。

public static class IntSumReducer
        extends Reducer<Text, IntWritable, Text, IntWritable> {
    private IntWritable result = new IntWritable();

    @Override
    public void reduce(Text key, Iterable<IntWritable> values,
                       Context context
    ) throws IOException, InterruptedException {
        int sum = 0;
        for (IntWritable val : values) {
            sum += val.get();
        }
        result.set(sum);
        context.write(key, result);
    }
}

//指定 Combiner 类
job.setCombinerClass(IntSumReducer.class);
job.setReducerClass(IntSumReducer.class);

Hadoop-MapReduce_第5张图片

五、OutputFormat数据输出

1. OutputFormat接口实现类

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

  1. OutputFormat 实现类
    Hadoop-MapReduce_第6张图片
  2. 默认输出格式:TextOutputFormat

2. 自定义OutputFormat

  1. 自定义一个类继承 FileOutputFormat 类,获取到输出流
  2. 自定义一个类继承 RecordWriter 类,重写 write() 方法,在该方法中实现具体的写出逻辑
  3. 在 Job 中设置 OutputFormat :job.setOutputFormatClass()

3. 案例

www 开头的网址和其它网址分别输出到两个文件中

输入文件:

www.baidu.com
www.taobao.com
www.bilibili.com
blog.csdn.net
leetcode-cn.com
github.com
gitee.com

自定义的 OutputFormat 类:

//指定输出类
job.setOutputFormatClass(WebsiteOutputFormat.class);

//自定义OutputFormat类
public static class WebsiteOutputFormat extends FileOutputFormat<Text, NullWritable> {

    @Override
    public RecordWriter<Text, NullWritable> getRecordWriter(TaskAttemptContext job) throws IOException, InterruptedException {
        //获取输出文件路径
        Path file = getOutputPath(job);

        //根据配置信息,获取设施系统
        FileSystem fs = file.getFileSystem(job.getConfiguration());

        //创建两个输出流
        FSDataOutputStream otherOs = fs.create(new Path(file.toString() + "/other.log"));
        FSDataOutputStream wwwOs = fs.create(new Path(file.toString() + "/www.log"));
        
        //创建一个 RecordWriter 对象
        RecordWriter<Text, NullWritable> recordWriter = new RecordWriter<Text, NullWritable>() {

            @Override
            public void write(Text key, NullWritable value) throws IOException, InterruptedException {
                if (key.toString().startsWith("www")) {
                    wwwOs.write(key.getBytes());
                    wwwOs.write("\n".getBytes());
                } else {
                    otherOs.write(key.getBytes());
                    otherOs.write("\n".getBytes());
                }
            }

            @Override
            public void close(TaskAttemptContext context) throws IOException, InterruptedException {
                IOUtils.closeStream(wwwOs);
                IOUtils.closeStream(otherOs);
            }
        };
        return recordWriter;
    }
}

六、Join应用

1. Reduce Join

  • Map 端的主要工作:为来自不同表或文件的 key/value 对,打标签以区别不同来源的记录。然后用连接字段作为 key,其余部分和新加的标志作为 value,最后进行输出。
  • Reduce 端的主要工作:在 Reduce 端以连接字段作为 key 的分组已经完成,我们只需要在每一个分组当中将那些来源于不同文件的记录(在 Map 阶段已经打标志)分开,最后进行合并就 ok 了。

案例:

Hadoop-MapReduce_第7张图片

代码:https://gitee.com/xinboss/bigdata/blob/master/Hadoop/src/main/java/com/atguigu/mapreduce/reduceJoin/ReduceJoinDemo.java

代码中需要注意的地方:

  1. Mapper 读取两个文件,在初始化方法 setup() 中(每读取一个文件执行一次)获取文件名
  2. Mapper 处理代码中,根据文件名,给数据打上标记,记录是来自哪个文件。
  3. 给自定义对象赋值时,值不能赋为 null,否则会在序列化时报 NullPointerException
  4. Reducer 中,传入的 value 的迭代器在迭代时,引用地址不变,即始终是一个对象,所以需要新创建一个对象,然后将 value 中的参数值拷贝过去。

2. Map Join

Map Join 适用于一张表十分小、一张表很大的场景。在 Reduce 端处理过多的表,非常容易产生数据倾斜。因此就使用到了 Map Join:在 Map 端缓存多张表,提前处理业务逻辑,这样增加 Map 端业务,减少 Reduce 端数据的压力,尽可能的减少数据倾斜。

案例,同Reduce Join

代码:https://gitee.com/xinboss/bigdata/blob/master/Hadoop/src/main/java/com/atguigu/mapreduce/mapJoin/MapJoinDemo.java

代码中需要注意的地方:

  1. 设置要缓存的文件:
    //缓存普通文件到 MapTask 运行节点
    job.addCacheFile(new URI("file:///input/reduceJoin/pd.txt"));
    job.addCacheFile(new URI("file:///D:/input/tablecache/pd.txt"));
    //如果是 Hadoop 集群运行,则设置HDFS路径
    job.addCacheFile(new URI("hdfs://hadoop102:8020/cache/pd.txt"));
    
  2. 在 Mapper 的 setup() 方法中,将缓存文件解析到 HashMap 中,方便后面 map() 方法中直接调用

七、Hadoop数据压缩

1. 概述

压缩的好处和坏处:

  • 优点:以减少磁盘IO、减少磁盘存储空间。
  • 缺点:增加CPU开销。

压缩原则:

  • 运算密集型的Job,少用压缩
  • IO密集型的Job,多用压缩

2. MR支持的压缩编码

1)压缩算法对比介绍

压缩格式 Hadoop是否自带 算法 文件扩展名 是否可切片 换成压缩格式后,原来的程序是否需要修改
DEFLATE 是,直接使用 DEFLATE .deflate 和文本处理一样,不需要修改
Gzip 是,直接使用 DEFLATE .gz 和文本处理一样,不需要修改
bzip2 是,直接使用 bzip2 .bz2 和文本处理一样,不需要修改
LZO 否,需要安装 LZO .lzo 需要建索引,还需要指定输入格式
Snappy 是,直接使用 Snappy .snappy 和文本处理一样,不需要修改

2)压缩性能的比较

压缩算法 原始文件大小 压缩文件大小 压缩速度 解压速度
gzip 8.3GB 1.8GB 17.5MB/s 58MB/s
bzip2 8.3GB 1.1GB 2.4MB/s 9.5MB/s
LZO 8.3GB 2.9GB 49.3MB/s 74.6MB/s

Snappy 官方介绍:http://google.github.io/snappy/

Snappy is a compression/decompression library. It does not aim for maximum compression, or compatibility with any other compression library; instead, it aims for very high speeds and reasonable compression. For instance, compared to the fastest mode of zlib, Snappy is an order of magnitude faster for most inputs, but the resulting compressed files are anywhere from 20% to 100% bigger.On a single core of a Core i7 processor in 64-bit mode, Snappy compresses at about 250 MB/sec or more and decompresses at about 500 MB/sec or more.

Snappy是一个压缩/解压缩库。它不以最大压缩或与任何其他压缩库兼容为目标;相反,它的目标是非常高的速度和合理的压缩。例如,与zlib的最快模式相比,Snappy对于大多数输入来说要快一个数量级,但产生的压缩文件要大20%到100%。在64位模式的core i7处理器的单核上,Snappy以大约250 MB/秒或更高的速度压缩,以大约500 MB/秒或更高的速度解压缩。

3. 压缩方式选择

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

  • Gzip压缩
    优点:压缩率比较高;
    缺点:不支持 Split(切片);压缩/解压速度一般;
  • Bzip2压缩
    优点:压缩率高;支持 Split(切片);
    缺点:压缩/解压速度慢。
  • Lzo压缩
    优点:压缩/解压速度比较快;支持 Split(切片);
    缺点:压缩率一般;想支持切片需要额外创建索引。
  • Snappy压缩
    优点:压缩和解压缩速度快;
    缺点:不支持 Split(切片);压缩率一般;

4. 压缩位置选择

压缩可以在 MapReduce 作用的任意阶段启用。
Hadoop-MapReduce_第8张图片

5. 压缩参数配置

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

压缩格式 对应的编码/解码器
DEFLATE org.apache.hadoop.io.compress.DefaultCodec
gzip org.apache.hadoop.io.compress.GzipCodec
bzip2 org.apache.hadoop.io.compress.BZip2Codec
LZO com.hadoop.compression.lzo.LzopCodec
Snappy org.apache.hadoop.io.compress.SnappyCodec

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

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

6. 案例

1)Map输出端采用压缩

即使你的 MapReduce 的输入输出文件都是未压缩的文件,你仍然可以对 Map 任务的中间结果输出做压缩,因为它要写在硬盘并且通过网络传输到 Reduce 节点,对其压缩可以提高很多性能,这些工作只要设置两个属性即可。

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);
job.set.....//省略

2)Reduce输出端采用压缩

// 设置reduce端输出压缩开启
FileOutputFormat.setCompressOutput(job, true);

// 设置压缩的方式
FileOutputFormat.setOutputCompressorClass(job, BZip2Codec.class); 
//FileOutputFormat.setOutputCompressorClass(job, GzipCodec.class); 
//FileOutputFormat.setOutputCompressorClass(job, DefaultCodec.class); 

八、MapReduce生产经验

1 使用 Sort 程序测试 MapReduce 计算性能

使用 Sort 程序评测 MapReduce

注:一个虚拟机不超过150G磁盘尽量不要执行这段代码

  1. 使用 RandomWriter 来产生随机数,每个节点运行10个Map任务,每个Map产生大约1G大小的二进制随机数

    $ hadoop jar $HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-examples-3.3.1.jar randomwriter random-data
    
  2. 执行Sort程序

    $ hadoop jar $HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-examples-3.3.1.jar sort random-data sorted-data
    
  3. 验证数据是否真正排好序了

    $ hadoop jar $HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-client-jobclient-3.3.1-tests.jar testmapredsort -sortInput random-data -sortOutput sorted-data
    

MapReduce 跑的慢, 程序效率的瓶颈在于两点:

  1. 计算机性能:CPU、内存、磁盘、网络
  2. I/O操作优化:
    1. 数据倾斜
    2. Map运行时间太长,导致Reduce等待过久
    3. 小文件过多

2. MapReduce常用调优参数

1)Map阶段

Hadoop-MapReduce_第9张图片

  1. 自定义分区,减少数据倾斜;

    定义类,继承 Partitioner 接口,重写 getPartition 方法

  2. 减少溢写的次数

    mapreduce.task.io.sort.mb:Shuffle的环形缓冲区大小,默认100m,可以提高到200m
    mapreduce.map.sort.spill.percent:环形缓冲区溢出的阈值,默认80% ,可以提高的90%

  3. 增加每次Merge合并次数

    mapreduce.task.io.sort.factor:默认10,可以提高到20

  4. 在不影响业务结果的前提条件下可以提前采用 Combiner

    job.setCombinerClass(xxxReducer.class);
    
  5. 为了减少磁盘IO,可以采用 Snappy 或者 LZO 压缩

    conf.setBoolean("mapreduce.map.output.compress", true);
    conf.setClass("mapreduce.map.output.compress.codec", SnappyCodec.class,CompressionCodec.class);
    
  6. 增加堆内存

    mapreduce.map.memory.mb:默认MapTask内存上限1024MB。可以根据128m数据对应1G内存原则提高该内存。
    mapreduce.map.java.opts:控制MapTask堆内存大小。(如果内存不够,报:java.lang.OutOfMemoryError)

  7. mapreduce.map.cpu.vcores:默认MapTask的CPU核数1。计算密集型任务可以增加CPU核数

  8. 异常重试

    mapreduce.map.maxattempts:每个Map Task最大重试次数,一旦重试次数超过该值,则认为Map Task运行失败,默认值:4。根据机器性能适当提高。

2)Reduce阶段

Hadoop-MapReduce_第10张图片

  1. mapreduce.reduce.shuffle.parallelcopies:每个 Reduce 去 Map 中拉取数据的并行数,默认值是 5。可以提高到 10。
  2. mapreduce.reduce.shuffle.input.buffer.percent:Buffer 大小占 Reduce 可用内存的比例,默认值 0.7。可以提高到 0.8
  3. mapreduce.reduce.shuffle.merge.percent:Buffer 中的数据达到多少比例开始写入磁盘,默认值0.66。可以提高到0.75
  4. mapreduce.reduce.memory.mb:默认 ReduceTask 内存上限 1024MB,根据128m数据对应1G内存原则,适当提高内存到4-6G
  5. mapreduce.reduce.java.opts:控制 ReduceTask 堆内存大小。(如果内存不够,报:java.lang.OutOfMemoryError)
  6. mapreduce.reduce.cpu.vcores:默认 ReduceTask 的CPU核数1个。可以提高到2-4个
  7. mapreduce.reduce.maxattempts:每个 ReduceTask 最大重试次数,一旦重试次数超过该值,则认为Map Task运行失败,默认值:4。
  8. mapreduce.job.reduce.slowstart.completedmaps:当 MapTask 完成的比例达到该值后才会为 ReduceTask 申请资源。默认是0.05。
  9. mapreduce.task.timeout:如果一个Task在一定时间内没有任何进入,即不会读取新的数据,也没有输出数据,则认为该Task处于Block状态,可能是卡住了,也许永远会卡住,为了防止因为用户程序永远Block住不退出,则强制设置了一个该超时时间(单位毫秒),默认是600000(10分钟)。如果你的程序对每条输入数据的处理时间过长,建议将该参数调大。
  10. 如果可以不用 Reduce,尽可能不用

3. MapReduce数据倾斜问题

数据倾斜现象:

  • 数据频率倾斜——某一个区域的数据量要远远大于其他区域。
  • 数据大小倾斜——部分记录的大小远远大于平均值。

减少数据倾斜的方法:

  1. 首先检查是否空值过多造成的数据倾斜
    生产环境,可以直接过滤掉空值;如果想保留空值,就自定义分区,将空值加随机数打散。最后再二次聚合。
  2. 能在 map 阶段提前处理,最好先在 Map 阶段处理。如:Combiner、MapJoin
  3. 设置多个 reduce 个数

九、Hadoop小文件优化方法

1. Hadoop小文件弊端

HDFS上每个文件都要在NameNode上创建对应的元数据,这个元数据的大小约为150byte,这样当小文件比较多的时候,就会产生很多的元数据文件,一方面会大量占用NameNode的内存空间,另一方面就是元数据文件过多,使得寻址索引速度变慢。
小文件过多,在进行MR计算时,会生成过多切片,需要启动过多的MapTask。每个MapTask处理的数据量小,导致MapTask的处理时间比启动时间还小,白白消耗资源。

2. Hadoop小文件解决方案

1)在数据采集的时候,就将小文件或小批数据合成大文件再上传HDFS(数据源头)

2)Hadoop Archive(存储方向)

是一个高效的将小文件放入HDFS块中的文件存档工具,能够将多个小文件打包成一个HAR文件,从而达到减少NameNode的内存使用

3)CombineTextInputFormat(计算方向)

CombineTextInputFormat用于将多个小文件在切片过程中生成一个单独的切片或者少量的切片。

4)开启uber模式,实现JVM重用(计算方向)

默认情况下,每个 Task 任务都需要启动一个 JVM 来运行,如果 Task 任务计算的数据量很小,我们可以让同一个 Job 的多个 Task 运行在一个 JVM 中,不必为每个 Task 都开启一个 JVM。

  1. 未开启 uber 模式,在 /input 路径上上传多个小文件并执行 wordcount 程序

    $ hadoop jar $HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-examples-3.3.1.jar wordcount /input /output2
    
  2. 观察控制台日志
    在这里插入图片描述

  3. 观察 http://hadoop103:8088/cluster

    Hadoop-MapReduce_第11张图片

  4. 开启 uber 模式,在 mapred-site.xml 中添加如下配置

    
    <property>
      	<name>mapreduce.job.ubertask.enablename>
      	<value>truevalue>
    property>
    
     
    <property>
      	<name>mapreduce.job.ubertask.maxmapsname>
      	<value>2value>
    property>
    
    <property>
      	<name>mapreduce.job.ubertask.maxreducesname>
      	<value>1value>
    property>
    
    <property>
      	<name>mapreduce.job.ubertask.maxbytesname>
      	<value>value>
    property>
    
  5. 分发配置

  6. 再次执行 wordcount 程序

    $ hadoop jar $HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-examples-3.3.1.jar wordcount /input /output2
    
  7. 观察日志
    在这里插入图片描述
    在这里插入图片描述

你可能感兴趣的:(大数据,hadoop,mapreduce,big,data)