mapreducer笔记

一、复习

一、linux
	1. 常用指令
		 cd pwd  ls
		 文件处理命令
		 系统操作命令
		 网络通信命令
		 软件管理指令
	2. 网络配置
    	ip
    	gateway
    	netmask
        DNS
    3. VIM/VI编辑器(重要)
    4. 免密登录认证(重要)
    5. shell script(重要)
二、HDFS
     1.hadoop分布式文件系统,主要解决了大数据集如何存储的问题。
     2.hdfs是hadoop的三大重要模块之一
     3.hdfs是用java语言开发
     4.可以部署到廉价(商用)机器上,允许机器宕机(认为机器宕机是常态)
     5.采用了分布式的存储思想:将文件切分成固定大小的多个块,并以副本的形式将这些块存储到不同的机器上(所以说,机器可以宕机)(重要)
     6. 块的固定大小如何选择
     	     条件1: 尽可能的减少磁盘寻址时间,程序员认为1000ms:100ms认为是最优的,寻址时间在100ms左右
     	     条件2:现实情况与网速有关,视情况而定。
 	 7. HDFS的架构思想(重要)
 	 8. secondaryNameNode的检查点机制(重要)
 	 9. HDFS的读写流程(重要)
 	 10. HDFS的API	
三、zookeeper

二、Mapreduce的基础

2.1 为什么要学习Mapreduce

1. 单机资源受限,比如CPU,磁盘
2. 分布式计算的程序的复杂度特别高,难度大

mapreduce就是解决以上问题的。

2.2 Mapreduce的简介

2.2.1 简介
1. mapreduce是hadoop的三大重要模块之一
2. mapreduce是一个并发的计算和分析框架,用于计算和分析分布式文件系统上的大数据集。
3. 将计算划分为两个阶段:一个map(映射)阶段,一个reduce(归约)阶段
4. 该框架的开发灵感来源于google的《mapreduce》论文
5. 方便开发人员在不会分布式计算的情况下,开发运行程序。
2.2.2 优缺点
1. 优点
	- 适合离线数据处理
	- mapreduce编程简单
	- 扩展性良好
	- 高容错性
2. 缺点
	- 不适合实时计算(实时计算:毫秒级别/秒级别,离线计算:秒级别以上)
	- 不适合流式计算(mapreduce处理的数据是静态的,不是流式的数据)
	- 不适合DAG(有向图)计算

2.3 Mapreduce的核心思想(重点)

简单的一句话概括:“移动计算而非移动数据”。

整理:
程序员将自己写好的业务逻辑代码和mr自带的一些组件打包成计算程序,移动到有数据存储的节点上,这样可以利用多节点的cpu的并发能力,提高计算效率(怎么提高的?一减少数据移动的开销,二利用了并发计算原理)
mapreduce是分为两个阶段,map阶段处理的是块文件(原始文件),计算后的结果存储本地磁盘,reduce阶段要跨节点fetch属于自己要处理的数据,计算后的结果存储到hdfs上(当然也可以存储到客户端所在的本地磁盘)

2.4 Mapreduce的阶段介绍(重点)

2.4.1 Map阶段
map阶段处理的是原始数据,也就是块文件(处理的是本存储节点上的数据)。会将处理的块文件,以切片的形式进行逻辑运算。通过映射关系进行一一映射。map阶段会有多个mapTask,这些任务并发运行,互不干扰
默认情况下,按行进行映射成键值对,
原始块文件
   |
   |
K1,V1(有N个kv对,K1是行偏移量,v1是行记录,也就是行内容)
   |
   |
  map方法
   |
   | 每一对k1v1都会调用一次map方法,在map方法里进行处理,形成K2V2 
   |
 K2,V2  (存储到本地磁盘)
2.4.2 Reduce阶段
reduce阶段处理的是map阶段计算出来的数据(临时数据),reduce阶段也会有多个reduceTask,并发运行,互不干扰。reduce处理的数据通常都是要跨节点fetch属于自己处理的数据。

fetch属于自己的一堆K2,v2,先形成<K2,<v2,v2,v2>>
         |
         |
       reduce方法
         |
	     |  同一个k2调用一次reduce方法,在reduce方法里进行处理,形成K3,v3
	     |
       K3,V3(存储到HDFS上)

2.5 Mapreduce的入门案例演示(重点,绘图)

2.5.1 案例简介:wordcount程序
wordcount:使用mr思想对多个文件,或者是多个块进行统计单词的出现频率
2.5.2 图解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9YH2X6YT-1630742724421)(ClassNotes.assets/mr的wordcount.jpg)]

2.6 Mapreduce的编程模型

2.6.1 自定义Mapper类型
1. 自定义类名,继承Mapper类型
2. 定义K1,V1,K2,V2的泛型
3. 重写map方法
2.6.2 自定义Reducer类型
1. 自定义类名,继承Reducer类型
2. 定义K2,V2,K3,V3的泛型
3. 重写reduce方法
2.6.3 自定义Driver类型
1. 获取job对象
2. 指定驱动类型
3. 指定Mapper类型和Reducer类型
4. 指定map阶段的K2,V2类型
5. 指定reduce阶段的K3,V3类型
6. 指定分区的数量.....
7. 指定要统计的文件的输入路径
8. 指定要输出的位置路径
9. 提交程序
2.6.4 wordcount案例代码步骤如下:

步骤1:创建项目,导入jar包依赖

<dependencies>
    <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-common</artifactId>
        <version>2.7.6</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-client -->
    <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-client</artifactId>
        <version>2.7.6</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-hdfs -->
    <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-hdfs</artifactId>
        <version>2.7.6</version>
    </dependency>
</dependencies>

步骤2:编写mapper类型

package com.qianfeng.mr.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;

/**
 * 1: 自定义类名,继承Mapper类型
 * 2: 定义K1,V1,K2,V2的泛型
 *       K1  行偏移量  整形   LongWritable     K1和V1是底层默认的类型,不能随便改
 *       V1  行内容    字符串  Text
 *       K2   单词     字符串  Text
 *       V2   数字1    整形   IntWritable
 * 3: 重写map方法
 */
public class MyMapper extends Mapper<LongWritable, Text,Text, IntWritable> {
    Text k2 = new Text();
    IntWritable v2 = new IntWritable(1);
    /**
     *
     * @param key    就是k1    单词频率统计时,行偏移量没有任何用处,无需操心
     * @param value   就是v1   只需要对v1做截断处理即可
     * @param context    上下文,该对象提供了输出k2,v2的方法
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        //1:将value转成java的String类型
        String lineRecord = value.toString();
        //2: 使用空格切分成数组
        String[] words = lineRecord.split(" ");
        //3:遍历数组,将元素作为K2,  数字1作为V2写出去
        for (String word : words) {
            //将word转成Text类型
            k2.set(word);
            //写出去
            context.write(k2,v2);
        }
    }
}

步骤3:编写reducer类型

package com.qianfeng.mr.wordcount;

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

import java.io.IOException;

/**
 * 1: 自定义类名,继承Reducer类型
 * 2: 规定k2,v2,k3,v3的泛型
 *       k2,v2和map阶段的输出数据的泛型一致
 *       k3:  单词    Text
 *       V3:  数字    IntWritable
 * 3: 重写reduce方法
 */
public class MyReducer extends Reducer<Text, IntWritable,Text,IntWritable> {
    /**
     *      <hello,<1,1,1,1,1,1>>
     * @param key   就是k2
     * @param values   同一个k2的多个v2
     * @param context  上下文
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
         //1: 将values的多个1进行累加
        int sum = 0;
        for (IntWritable value : values) {
            //累加
            sum+=value.get();
        }
        //累加的结果sum就是v3, 转成IntWritable
        IntWritable v3 = new IntWritable(sum);
        //k2,就是k3,写出去
        context.write(key,v3);
    }
}

步骤4:编写driver类型

package com.qianfeng.mr.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.File;
import java.io.IOException;


public class MyDriver {
    public static void main(String[] args) {

        try {
            Configuration configuration = new Configuration();
            Job job = Job.getInstance(configuration);
            //设置驱动类
            job.setJarByClass(MyDriver.class);
            //设置mapper和reducer类型
            job.setMapperClass(MyMapper.class);
            job.setReducerClass(MyReducer.class);
            //设置k2,v2,k3,v3的泛型
            job.setMapOutputKeyClass(Text.class);
            job.setMapOutputValueClass(IntWritable.class);
            job.setOutputKeyClass(Text.class);
            job.setOutputValueClass(IntWritable.class);
            //设置分区数量,不设置默认为1
            job.setNumReduceTasks(1);
            //设置输入和输出路径
            Path inputPath = new Path("D:/test");
            FileInputFormat.addInputPath(job,inputPath);
            FileOutputFormat.setOutputPath(job,new Path("D:/output"));

            //提交等待完成
            System.exit(job.waitForCompletion(true)?0:1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2.7 Partitioner组件的应用(重点)

2.7.1 reduceTask数量的研究
0.job.setNumReduceTasks()可以指定reduceTask的数量,实质上是指定输出的文件个数。
1. reduceTask的数量由分区数量决定
2. 不是每一个分区都有数据的
3. 如果自定义分区器了,那么reduceTask的数量不能小于分区器里的分区数量
4. reduceTask设置0时,也就是没有reduceTask,表明没有reduce阶段,只有map阶段,map阶段产生的数据为永久数据,文件名为part-m-.....,文件里的数据无序,也就是说没有进入环形缓冲区
2.7.2 分区器的简介与自定义
mapreduce的默认分区器是HashPartitioner。 逻辑如下:调用k2的hash值与int的最大值做位运算再与numReduceTasks做取模运算。

public class HashPartitioner<K, V> extends Partitioner<K, V> {

  /** Use {@link Object#hashCode()} to partition. */
  public int getPartition(K key, V value,
                          int numReduceTasks) {
    return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;
  }

}

所以说,如果想要控制如何分区,可以自定义分区器,那么如何自定义分区器,可以参考HashPartitioner
案例演示
package com.qianfeng.mr.wordcount;

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

/**
 * 如何自定义分区器:
 * 1: 继承org.apache.hadoop.mapreduce.Partitioner
 * 2: 规定k2,v2的泛型
 * 3: 重写getPartition方法
 *     注意:该方法的返回值是一个int,表示分区号,是一个从0开始的自然数,多个分数号不能间断。必须是连续的
 *
 *
 *   需求: 将要统计的wordcount分为三个区:
 *         aA-nN为0分区的数据
 *         oO-zZ为1分区的数据
 *         其他为2分区的数据
 */
public class MyPartitioner extends Partitioner<Text, IntWritable> {
    @Override
    public int getPartition(Text text, IntWritable intWritable, int numPartitions) {
        //将k2变成字符串
        String k2 = text.toString();
        String first = k2.charAt(0)+"";
        if(first.matches("[A-Na-n]")){
            return 0;
        }else if(first.matches("[o-zO-Z]")) {
            return 1;
        }else{
            return 2;
        }
    }
}

-----------day02------------------

2.8 IDE运行Mapreduce的几种模式(熟悉)

2.8.1 说明
集成开发工具(IDE)运行mapreduce的模式有三种,分别是
1. 在本地运行计算本地的文件,
2. 在本地运行计算HDFS上的文件,
3. 将计算程序远程提交到HDFS上计算HDFS上的文件。
2.8.2 本地运行计算本地的文件
本地指的是IDE所在的操作系统环境,计算的是本地文件系统中的文件。

原理如下:
- Configuration对象会读取四个默认的配置文件。
- fs.defaultFS的值为file:///,获取的是本地文件系统对象。
- mapreduce.framework.name的值为local

所以,在Driver中的输入路径和输出路径指定为本地的路径即可
2.8.3 本地运行计算HDFS上的文件
本地指的是IDE所在的操作系统环境,计算的是HDFS中的文件。

原理如下:
- 既然想要获取HDFS上的文件,那么fs.defaultFS的值为hdfs://qianfeng01:8020,也就是要获取分布式文件系统对象
- 是访问HDFS,将对应的文件读取到本地内存进行计算,计算结果存储到HDFS上(因为还是使用mapreudce.framework.name的默认值local).
2.8.4 本地远程提交计算程序到HDFS上,计算HDFS上的文件
原理:
- 移动计算而非移动数据
- 使用的是HDFS集群的设置,mapreduce.framework.name的值是yarn
- IDE需要有权限访问HDFS,IDE需要设置跨平台操作
   conf.set("mapreduce.app-submission.cross-platform", "true");
   System.setProperty("HADOOP_USER_NAME", "root");
   
步骤:
1. 将计算程序打包成jar文件,放入到classpath下: add as Library
2. 将集群的mapred-site.xml和yarn-site.xml文件导入到项目的resouces目录(注意重启的问题)
3. 如果报: 无任务控制....等字样,说明没有设置跨平台属性
4. 程序中的路径要使用hdfs上的路径。   
2.8.5 扩展:
将程序打成jar包,上传到hdfs上,使用hadoop jar指令来运行程序

注意:杀死yarn上的任务指令

]# yarn application -kill appId

appID就是网址8088上的ID

三、Hadoop序列化机制

3.1 序列化的概念和应用领域

-- 序列化:
	将内存中的对象转成二进制的字节序列形式
-- 反序列化:
	讲二进制形式的字节序列转成内存中的对象
-- 应用领域
   (1) 持久化到磁盘上保存
   (2) 网络传输

3.2 Hadoop的常用类型

java的常用的数据类型对应的hadoop数据序列化类型

Java类型Hadoop Writable类型释义
booleanBooleanWritable标准布尔型数值
byteByteWritable单字节数值
intIntWritable整型数值
floatFloatWritable单精度数
longLongWritable长整型数值
doubleDoubleWritable双精度数
stringText使用UTF8格式存储的文本
mapMapWritable以键值对的形式存储Writable类型的数据
arrayArrayWritable以数组的形式存储Writable类型的数据
nullNullWritable当<key,value>中的key或value为空时使用

NullWritable说明:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pna7bBph-1630742724424)(ClassNotes.assets/image-20210831111622649.png)]

3.3 序列化接口和排序接口(重点理解)

3.3.1 序列化接口:Writable
该接口只有两个抽象方法,分别是序列化方法和反序列化方法。

public interface Writable {
 //序列化方法,将对象序列化到out流中
  void write(DataOutput out) throws IOException;
  //反序列化方法,从in流中读取信息转成对象。
  void readFields(DataInput in) throws IOException;
}
3.3.2 排序接口:WritableComparable
hadoop的排序接口,继承了序列化接口,同时继承了java的排序接口

public interface WritableComparable<T> extends Writable, Comparable<T> {
}

3.4 如何自定义Hadoop类型

因为hadoop的类型一定会进行网络传输,比如在reduceTask抓取的时候,还有在环形缓冲区的时候要比较,排序等。因此hadoop的类型,一定要实现序列化接口和排序接口。

方式1:  可以单独实现Writable和Comparable接口
方式2:  可以直接实现WritableComparable接口

步骤如下:
1. 定义类型,使用上述的某一种方式
2. 

3.5 Hadoop与java序列化机制的比较

3.5.1 java序列化机制
是一个重量级的序列化机制,在序列化过程中,除了属性的值外,还会涉及到继承体系,类的结构,header等这些额外的,因此在网络传输过程中,信息体积大,速度相对慢等特点
3.5.2 hadoop序列化机制
因为hadoop一定会涉及到网络传输,因此hadoop序列化的要求应该是体积小,速度快,占用带宽小。  所以有以下特征:

1. 紧凑:  数据在序列化时比较紧凑,节省带宽资源
2. 快速:  在序列化和反序列化时,要尽可能的节省资源开销(磁盘,cpu,内存)
3. 可扩展: 要适应市场新的变化。扩展性要好。
4. 支持互操作:能支持不同语言写的客户端和服务端进行交互;

需要注意的是: MapReduce的key和value,都必须是可序列化的。而针对于key而言,是数据排序的关键字,因此还需要提供比较接口:WritableComparable

3.6 案例之手机流量的统计(熟悉)

3.6.1 程序需求分析:
统计每个手机的总上行流量,总下行流量,总流量
3.6.2 数据准备
参考文件:HTTP_20130313143750.dat
3.6.3 代码如下:

步骤1:设计FlowBean类型

package com.qianfeng.mr.flow;

import org.apache.hadoop.io.WritableComparable;

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

/**
 * 步骤1:自定义一个hadoop类型,用于存储某一个手机号的信息
 * 1: 手机号
 * 2: 上行流量
 * 3: 下行流量
 * 4: 总流量
 *
 * 步骤2: 实现序列化排序接口,重写方法
 */
public class FlowBean implements WritableComparable<FlowBean> {
    private String phoneNum;
    private long upFlow;
    private long downFlow;
    private long totalFlow;

    public FlowBean(){}

    public FlowBean(String phoneNum, long upFlow, long downFlow, long totalFlow) {
        this.phoneNum = phoneNum;
        this.upFlow = upFlow;
        this.downFlow = downFlow;
        this.totalFlow = totalFlow;
    }

    public String getPhoneNum() {
        return phoneNum;
    }

    public void setPhoneNum(String phoneNum) {
        this.phoneNum = phoneNum;
    }

    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 getTotalFlow() {
        return upFlow+downFlow;
    }

    public void setTotalFlow(long totalFlow) {
        this.totalFlow = totalFlow;
    }

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

    @Override
    public void readFields(DataInput in) throws IOException {
        phoneNum = in.readUTF();
        upFlow = in.readLong();
        downFlow = in.readLong();
        totalFlow = in.readLong();
    }

    @Override
    public int compareTo(FlowBean o) {
        return 0;
    }
}

步骤2:设计FlowMapper

package com.qianfeng.mr.flow;

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

import java.io.IOException;

/**
 *   K1是行偏移量
 *   V1是行内容
 *   K2想要手机号
 *   V2想要上行,下行,总流量,所以是FlowBean类型
 */
public class FlowMapper extends Mapper<LongWritable, Text,LongWritable,FlowBean> {
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        String line = value.toString();
        String[] content = line.split("\t");
        //获取手机号部分
        String phoneNum = content[1];
        LongWritable phoneK2 = new LongWritable(Long.parseLong(phoneNum));
        //获取上行,下行
        String upflow = content[content.length-3];
        String downflow = content[content.length-2];
        FlowBean v2 = new FlowBean(phoneNum,Long.parseLong(upflow),Long.parseLong(downflow));

        context.write(phoneK2,v2);

    }
}

步骤3:设计FlowReducer

package com.qianfeng.mr.flow;

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

import java.io.IOException;

/**
 *   137...5566        <123   234   357>     <111   222   333>
 */
public class FlowReducer extends Reducer<LongWritable,FlowBean,LongWritable,FlowBean> {
    @Override
    protected void reduce(LongWritable key, Iterable<FlowBean> values, Context context) throws IOException, InterruptedException {
        long upFlow = 0;
        long downflow = 0;
        for (FlowBean value : values) {
            upFlow += value.getUpFlow();
            downflow+= value.getDownFlow();
        }
        //再次封装成一个新的flowBean对象
        FlowBean v3 = new FlowBean(key.get()+"",upFlow,downflow);
        //写出去
        context.write(key,v3);
    }
}

步骤4:设计FlowDriver

package com.qianfeng.mr.flow;

import com.qianfeng.mr.wordcount.MyMapper;
import com.qianfeng.mr.wordcount.MyPartitioner;
import com.qianfeng.mr.wordcount.MyReducer;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
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;


public class FlowDriver {
    public static void main(String[] args) {

        try {
            Configuration configuration = new Configuration();
            Job job = Job.getInstance(configuration);
            //设置驱动类
            job.setJarByClass(FlowDriver.class);
            //设置mapper和reducer类型
            job.setMapperClass(FlowMapper.class);
            job.setReducerClass(FlowReducer.class);
            //设置k2,v2的泛型   ,  如果k2和k3相同,那么可以不指定k2    如果v2和v3相同,那么可以不指定v2
            job.setMapOutputKeyClass(LongWritable.class);
            job.setMapOutputValueClass(FlowBean.class);
            // 设置,k3,v3的泛型
            job.setOutputKeyClass(LongWritable.class);
            job.setOutputValueClass(FlowBean.class);
            //设置分区数量,不设置默认为1
            job.setNumReduceTasks(1);
            //设置输入和输出路径
            Path inputPath = new Path("D:\\academia\\The teaching material\\The required data\\data-mr\\flow");
            FileInputFormat.addInputPath(job,inputPath);
            Path outPath = new Path("D:/output");
            FileSystem fs = FileSystem.get(configuration);
            if(fs.exists(outPath)){
                fs.delete(outPath,true);
            }
            FileOutputFormat.setOutputPath(job,outPath);

            //提交等待完成
            System.exit(job.waitForCompletion(true)?0:1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

四、MapReduce的进阶

4.1 Mapreduce运行流程概述(熟悉)

##1. 整个mr运行过程中有三大类进程:
	(1)MRAPPMaster: 负责整个作业的资源调度和状态协调。每一个mr程序在启动时都会启动一个MRAPPMaster
	(2)MapTask:负责Map阶段的数据处理流程(是原始块文件的处理)
	(3)ReduceTask:负责Reduce阶段的数据处理流程(是Map阶段产生的临时数据)
##2 整体流程如下:
	(1)mr作业提交时,会为这个作业启动一个AppMaster进程,主类就是MRAPPMaster。
	(2)启动后会根据job上的描述信息(输入规则和输入路径)计算inputSplit(输入分片),从而决定MapTask的数量
	(3)AppMaster进程向resourcemanager为MapTask申请资源container(cpu,内存,磁盘)
	(4)MapTask运行时:
	  -- 根据输入规则(FileInputFormat)和记录阅读器(RecordReader)以及分片信息读取块文件,形成K1V1
	  -- K1V1作为map函数的输入数据,进入map函数,经过逻辑处理,形成K2V2
	  -- K2V2被写入到环形缓冲区,当达到阈值时,会进行溢写(Flush)成磁盘上文件,溢写前分区和排序。
	(5)AppMaster在监控MapTask运行5%时,会为ReduceTask申请资源,会根据用户指定的参数来启动相应的reduceTask进程,并告知reduceTask需要处理的数据范围
	(6)ReduceTask运行时:
		reduceTask会根据Appmaster告知自己的待处理的数据范围,从MapTask产生的临时数据中拉取属于自己的数据,并进行"归并排序",然后相同的key化为一组<K2,<ListV2>>,每一组调用一次reduce函数,经过逻辑处理后,形成K3V3,根据输出规则将数据写出到目的地。

4.2 运行流程之分片机制(重点)

4.2.1 输入分片的概念
MapReduce在进行作业提交时,会预先对将要分析的原始数据进行划分处理,形成一个个等长的逻辑数据对象,称之为输入分片(inputSplit),简称“分片”。MapReduce为每一个分片构建一个单独的MapTask,并由该任务来运行用户自定义的map方法,从而处理分片中的每一条记录。

a文件  1025MB  ---  9块,最后一块是1MB


分析:分片就是记录MapTask要处理的是哪一个块,通过属性记录,因此称之为逻辑数据。

扩展:FileSplit的源码解析

public class FileSplit extends InputSplit implements Writable {
  private Path file;     //记录分片对应的文件名
  private long start;    //记录该分片要处理的块文件的开始偏移量
  private long length;   //记录该分片要处理的数据大小
  private String[] hosts; // 记录该分片要处理的数据的位置。
  private SplitLocationInfo[] hostInfos;   //记录的是分片的位置信息

  public FileSplit() {}
    //........
}    
4.2.2 分片大小如何选择

说明:分片大小指的是length属性。

#1. 分片数量越多,优势如下:
    -- 处理分片对应的原始数据所花的时间更少,也就是小于一个分片处理整个文件的时间。
    -- 每一个分片对应一个MapTask,MapTask是并行运行的,效率高
    -- 分片越多,负载均衡就越好。
    -- 计算机硬件越好,处理速度越快,就可以腾出时间,计算其他任务。
#2. 分片太小的缺点:
	如果分片太小,管理分片的总时间和构建map任务的总时间将决定作业的整个执行时间
#3. 分片太大的缺点:	
	如果分片跨越两个数据块,那么分片的部分数据需要通过网络传输到map任务运行的节点,占用网络带宽,效率更低
#4 得出结论:
	因此最佳分片大小应该和HDFS上的块大小一致。hadoop2.x默认128M.
4.2.3 源码解析:

FileInputFormat、TextInputFormat、LineRecordReader这三个类的源码,参考视频

##1. TextInputFormat类型源码解读
  --1. 是MR默认使用的输入格式,父类是FileInputFormat
  --2. RecordReader<LongWritable, Text>  createRecordReader() 
       说明: 此方法的作用是用于获取记录阅读器对象,默认是LineRecordReader
  --3. boolean isSplitable(JobContext context, Path file) 	
       说明:此方法的作用是判断此文件是否支持切分。此方法是在计算分片时调用的。
##2. LineRecordReader类型源码解读
  --1. 是MR的默认的记录阅读器对象,按行读取,一行封装成一对<k1,v1>
  --2. initialize(InputSplit genericSplit,TaskAttemptContext context)
	   说明:初始化方法的作用就是给对象里的属性(start,end,key,value,in等)赋值,从而定位到要读取的真正的原始块文件。需要注意的是start的值最终会舍弃分片的第一行,从新赋值
  --3. boolean nextKeyValue()     
	   说明:此方法的作用是真正去读取一行记录,然后将偏移量封装到K1,行记录封装到V1里。
	   注意:最后一个分片时,会多读一行,证明没有数据可读了。
4.2.4 分片机制的总结
1)分片大小参数

通过分析源码,在FileInputFormat中,计算切片大小的逻辑:Math.max(minSize, Math.min(maxSize, blockSize)); 切片主要由这几个值来运算决定

参数默认值属性
minsize1mapreduce.input.fileinputformat.split.minsize
maxsizeLong.MAXVALUEmapreduce.input.fileinputformat.split.maxsize
blocksize块大小dfs.blocksize:

可以看出,就是取minsize、maxsize、blocksize三者的中间的那个值。

--1. 将maxsize调得比blocksize小,则会让切片变小,而且就等于配置的这个参数的值.
--2. 将minsize调得比blockSize大,则会让切片变得比blocksize还大
--3. 但是,不论怎么调参数,都不能让多个小文件"划入"一个split
2)分片创建过程总结
1. 获取文件大小及位置
2. 判断文件是否可以分片(压缩格式有的可以进行分片,有的不可以)
3. 获取分片的大小
4. 剩余文件的大小/分片大小>1.1时,循环执行封装分片信息的方法,具体如下:
   封装一个分片信息(包含文件的路径,分片的起始偏移量,要处理的大小,分片包含的块的信息,分片中包含的块存在哪儿些机器上)

5. 剩余文件的大小/分片大小<=1.1且不等于0时,封装一个分片信息(包含文件的路径,分片的起始偏移量,要处理
   的大小,分片包含的块的信息,分片中包含的块存在哪儿些机器上)

分片的注意事项:1.1倍的冗余。

3)分片细节问题总结

如果有多个分片

- 第一个分片读到末尾再多读一行
- 既不是第一个分片也不是最后一个分片第一行数据舍弃,末尾多读一行
- 最后一个分片舍弃第一行,末尾多读一行
- 为什么:前一个物理块不能正好是一行结束的位置啊	

4)分片与块的区别

看完源码就知道了

1. 分片是逻辑数据,记录的是要处理的物理块的信息而已
2. 块是物理的,是真实存储在文件系统上的原始数据文件。

-----------day03—————

4.3 运行流程之MapTask(重点)

4.3.1 MapTask的整体概述
1. maptask调用FileInputFormat的createRecordReader通过分片数据来读取原始数据    
2. 会调用nextKeyValue方法获取每行数据,然后返回一个(K,V)对,K是offset,V是一行数据
3. 将k-v对交给Map函数进行处理
4. 每对k-v调用一次map(K,V,context)方法,经过处理,使用context.write(k,v)
5. 写出的数据交给收集器OutputCollector.collect()处理
6. 将数据写入环形缓冲区,并记录写入的起始偏移量,终止偏移量,环形缓冲区默认大小100M
7. 默认写到80%的时候要溢写到磁盘,溢写磁盘的过程中数据继续写入剩余20%
8. 溢写磁盘之前要先进行分区然后分区内进行排序
9. 默认的分区规则是hashpatitioner,即key的hash%reduceNum
10. 默认的排序规则是key的字典顺序,使用的是快速排序
11. 溢写会形成多个文件,在maptask读取完一个分片数据后,会将环形缓冲区数据刷写到磁盘
12. 将数据多个溢写文件进行合并,分区内排序(外部排序===》归并排序)

4.4 运行流程之ReduceTask(重点)

1. reduceTask启动后,开始使用线程fetch属于自己分区的map端产生的临时数据。
2. fetch过来的K2,v2,在内存中会进行归并排序。
3. reduceTask会调用分组器将内存中的k2,v2按照不同的k2来分组。
4. 每一组的<k2,list<v2>>调用一次reduce方法,经过处理,使用context.write(k,v)写出去。
5. 写出去的位置由FileOutputFormat的参数决定。

4.5 Shuffle流程(重点)

4.5.1 概念:
从map阶段的map函数输出开始,到reduce阶段的reduce函数接受数据为止,这个过程称之为shuffle阶段。
整个shuffle阶段可以划分为map端的shuffle和reduce端的shuffle.
4.5.2 map端shuffle
1. map函数的输出数据K2,v2进入环形缓冲区,环形缓冲区默认大小100MB,阈值80MB。
2. 当数据存储达到阈值时,会启动一个线程将该数据溢写出去,剩下的20M会继续写入,当20M写满,80M还未溢写完,则maptask出现阻塞。
3. 在溢写前,会先调用分区器按key分区,然后同一分区的数据进行按key排序,排序算法为QuickSort,规则为字典排序。(注意:分区和排序都是对KV对的元数据进行的)
4. 溢写时会产生临时文件,按照分区号从小到大将每一个分区的数据溢写到文件中(注意:写出时是通过排好序的元数据找到原始数据,写出去)。
5. 在排序后,溢写前,可以调用combiner函数来减少数据的溢写,目的减少磁盘IO.
6. 如果溢写文件数量有多个的话,会进行再次合并,合并成一个最终的临时文件,合并的时候使用的是归并算法。
7. 如果溢写文件数据至少3个,则在合并排序后,会再次调用combiner函数,来减少最终临时文件的大小。如果数量低于3个,则没有必要调用combiner。
8. 为了减少临时文件到reduce端的网络IO,建议将临时文件压缩,再进行传输。
4.5.3 reduce端shuffle
1. 当某一个mapTask结束后,reduceTask会利用线程开始fetch属于自己要处理的分区的数据,线程默认是5个,线程数量是针对于每一个节点来说的,线程通过HTTP协议抓取数据。
2. 如果抓过来的数据量过小,直接在对应的jvm的内存中进行归并排序,输入给reduce函数
3. 如果数据量过大,则会直接拷贝到所在的本地磁盘上,然后将多个文件合并成较大的文件,合并因子是10,合并时采用的算法是归并算法。(细节:最后一次合并一定要满足10这个因子,而且不会溢写成文件,直接输入给reduce).
4. 在归并算法合并时,如果map端的数据是压缩的,那么要在reduceTask的内存中解压缩在合并。
5. reduce处理后将数据存储到HDFS上。

4.6 Combiner函数

4.6.1 为什么要用Combiner函数
因为mapreduce在运行作业时,会涉及到磁盘IO和网络IO。而集群中资源都有限(磁盘空间,带宽)。因此在作业期间能减少磁盘IO和网络IO,是最优的。而Combiner函数就可以帮助用户来做到这点。
4.6.2 Combiner函数的特点
1. 本质就是运行在各个阶段的排序后的reduce函数。可以在map阶段,也可以在reduce阶段。
2. 是mapreduce作业的一个组件。
3. 父类是Reducer类型。
4. 减少IO
5. 应该在不影响结果的前提下来使用。
6. 使用job.setCombinerClass(.....)来设置

4.7 MapReduce优化参数

参考文档和视频解说

五、YARN资源管理器

5.1 MapReduce1.x版本的简介

参考文档

5.2 YARN的简介与设计思想(重点)

5.2.1 YARN的简介
1. Apache YARN(yet another resource Negotiator)是 Hadoop框架的资源管理系统,
2. 是Hadoop的三大核心模块之一。
3. 由于yarn的通用性比较好,所以除了mapreduce外,还可以在其上运行其他计算框架,比如spark,tez

扩展:有些软件是运行在mr,spark之上的,比如pig,hive等。
5.2.2 YARN的设计思想
yarn的设计思想是将资源管理和作业调度以及监视功能设计成独立的守护进程。一个全局的资源管理器ResourceManager,多个节点管理器NodeManager,针对于每一个提交的应用程序有一个applicationMaster进程。
资源管理器和节点管理器是长期运行的守护进程。

每个进程的作用如下:

resouceManager:   
		负责整个集群的资源管理和分配,内部有一个scheduler(资源调度器)。
nodeManager: 
		节点管理器,每个机器上只有一个。管理和监视本节点上的资源,并向resouceManager汇报
container(容器):
		指的就是节点上资源,比如cpu,内存,磁盘,网络等
applicationmaster:  
		简称appmaster, 一个应用程序对应一个appmaster, 应用程序结束,appmaster释放资源,并停止
		appmaster在工作时,负责向resourcemanager申请作业所需要的资源,并与nodemanager共同负
		责作业的运行

5.3 YARN的配置说明

5.4 YARN的job提交流程(重点)

5.5 YARN的三种调度器(熟悉)
e阶段。
2. 是mapreduce作业的一个组件。
3. 父类是Reducer类型。
4. 减少IO
5. 应该在不影响结果的前提下来使用。
6. 使用job.setCombinerClass(…)来设置


## 4.7 MapReduce优化参数

参考文档和视频解说


# 五、YARN资源管理器

## 5.1 MapReduce1.x版本的简介

参考文档


## 5.2 YARN的简介与设计思想(重点)

#### 5.2.1 YARN的简介

  1. Apache YARN(yet another resource Negotiator)是 Hadoop框架的资源管理系统,
  2. 是Hadoop的三大核心模块之一。
  3. 由于yarn的通用性比较好,所以除了mapreduce外,还可以在其上运行其他计算框架,比如spark,tez

扩展:有些软件是运行在mr,spark之上的,比如pig,hive等。


#### 5.2.2 YARN的设计思想

yarn的设计思想是将资源管理和作业调度以及监视功能设计成独立的守护进程。一个全局的资源管理器ResourceManager,多个节点管理器NodeManager,针对于每一个提交的应用程序有一个applicationMaster进程。
资源管理器和节点管理器是长期运行的守护进程。


每个进程的作用如下:

resouceManager:
负责整个集群的资源管理和分配,内部有一个scheduler(资源调度器)。
nodeManager:
节点管理器,每个机器上只有一个。管理和监视本节点上的资源,并向resouceManager汇报
container(容器):
指的就是节点上资源,比如cpu,内存,磁盘,网络等
applicationmaster:
简称appmaster, 一个应用程序对应一个appmaster, 应用程序结束,appmaster释放资源,并停止
appmaster在工作时,负责向resourcemanager申请作业所需要的资源,并与nodemanager共同负
责作业的运行


5.3 YARN的配置说明

5.4 YARN的job提交流程(重点)

5.5 YARN的三种调度器(熟悉)
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值