Hadoop基础【MapReduce框架原理】

一、框架原理

MapReduce的数据流

数据如何进行分份?

如果一份文件300M,若平均分配,会引起网络传输,而Shuffle阶段非常依赖于网络带宽,这一阶段又很重要,直接决定了MapReduce程序的性能,所以为了提高Shuffle的性能,会尽量将网络带宽交给shuffle来用,其他时间一般不要使用网络传输,所以如果按照块大小的128M来切,会大大提高任务的性能。

任务的切片是在Yarn的客户端完成,也就是Driver。

任务提交源码:

job提交中做了什么事情:检查job的状态(DEFINE),设置新的API,连接集群,向集群提交job(检查job的输出,获取临时文件夹,获取job的ID,向临时文件夹中写jar包,写切片信息,写配置文件,最终提交任务,结束)

切片过程:切片发生在默认的任务提交过程中,根据切片的数量启动map的数量,如何切片:获取具体job的配置信息,从job的配置中获取inputFormatClass(默认为TestInputFormat),根据类对象,通过反射的方式生成InputFormatFormat对象,调用InputFormat对象的getSplits方法来切片(计算minSize和maxSize默认为1和long的最大值,然后开始生成切片,使用ArrayList存放切片,获取输入目录的文件列表,遍历文件,并且对每个文件进行切片,如果是目录则不处理,获取文件路径,获取文件长度,如果长度为0则不切片,判断文件是否可以进行切片【如果是文本文件,一定可以进行切片,如果是压缩文件,要判断压缩编码是否支持切片】,获取文件的块大小,根据BlockSize,minSize,maxSize三者判断切片的大小【大小判断规则:max(minSize,min(maxSize,BlockSize))】,记录当前文件还有多长没有被切片记为bytesRemaining,当bytesRemaing / splitSize > 1.1时可以切片【防止map的浪费,如大于1,则128.1M的文件会被切成两片,现在当剩余文件小于128M + 12.8M时,不会再启动一个map,节省资源】),将切片的结果放在list中,之后将list换成数组,将数组信息写出去,返回数组的长度。

InputFormat类是所有InputFormat的抽象父类,工作之一是将输入进来的文件从逻辑上划分成几个set,第二个工作是对每一个切片创建一个record reader(切片在客户端完成,客户端调用InputFormat,将数据切分成很多片,此时的片还不是KV值,每一个切片分配一个MapTask进行处理,创建record reader的过程发生在MapTask中),record reader把数据打碎成KV值输入给Mapper。

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

自定义InputFormat:需求,将整个文件转化成KV对。

        自定义几个小文件(大于三个),将这几个小文件转化成一个KV文件。

        MyInputFormat类

package com.hike.mr.inputformat;

import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.RecordReader;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;

import java.io.IOException;

public class MyInputFormat extends FileInputFormat<Text, BytesWritable> {
    /**
     * 返回一个自定义的RecordReader
     * @param inputSplit
     * @param taskAttemptContext
     * @return
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    public RecordReader<Text, BytesWritable> createRecordReader(InputSplit inputSplit, TaskAttemptContext taskAttemptContext) throws IOException, InterruptedException {
        return new MyRecordReader();
    }
}

        MyRecordReader类

package com.hike.mr.inputformat;

import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.RecordReader;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.apache.hadoop.mapreduce.lib.input.FileSplit;
import org.apache.logging.log4j.message.ReusableMessage;

import java.io.IOException;

/**
 * 负责将整个文件转化成一组KV对
 */
public class MyRecordReader extends RecordReader<Text, BytesWritable> {

    //表示文件读完了,默认为false,表示文件没读过
    private boolean isRead = false;

    //KV对
    private Text key = new Text();
    private BytesWritable value = new BytesWritable();

    private FSDataInputStream inputStream;
    private FileSplit fs;


    /**
     * 初始化方法,一般执行一些初始化操作
     * @param inputSplit
     * @param taskAttemptContext
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    public void initialize(InputSplit inputSplit, TaskAttemptContext taskAttemptContext) throws IOException, InterruptedException {
        //开流
        fs = (FileSplit) inputSplit;    //转化成子类,inputSplit是最抽象的方法
        FileSystem fileSystem = FileSystem.get(taskAttemptContext.getConfiguration());  //获取配置信息,不要new,因为Driver中已经new过了
        inputStream = fileSystem.open(fs.getPath());    //转化成子类才能够获取到路径,父类是无法获取路径的
    }

    /**
     * 读取下一个指针指向的KV对
     * @return 读到了返回false,没读到返回true,因为读到,指针到了最后,再读就没有值了,所以返回false
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    public boolean nextKeyValue() throws IOException, InterruptedException {
        if(!isRead){
            //读取文件
            //填充K
            key.set(fs.getPath().toString());
            // 填充V
            byte[] buffer = new byte[(int) fs.getLength()];
            inputStream.read(buffer);

            value.set(buffer,0,buffer.length);
            //标记文件读完
            isRead = true;
            return true;
        } else {
            return false;
        }
    }

    /**
     * 获取当前读到的key
     * @return
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    public Text getCurrentKey() throws IOException, InterruptedException {
        return key;
    }

    /**
     * 获取当前读到的value
     * @return
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    public BytesWritable getCurrentValue() throws IOException, InterruptedException {
        return value;
    }

    /**
     * 显示已经完成了多少数据,是一个0.0 - 1.0 的数字
     * @return
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    public float getProgress() throws IOException, InterruptedException {
        return isRead ? 1 : 0;
    }

    /**
     * 关闭方法,一般用来关闭资源
     * @throws IOException
     */
    @Override
    public void close() throws IOException {
        //关流
        IOUtils.closeStream(inputStream);
    }
}

        MyInputDriver类

package com.hike.mr.inputformat;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.BytesWritable;
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 org.apache.hadoop.mapreduce.lib.output.SequenceFileOutputFormat;

import java.io.IOException;

public class MyInputDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        Job job = Job.getInstance(new Configuration());

        job.setJarByClass(MyInputDriver.class);

        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(BytesWritable.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(BytesWritable.class);

        job.setInputFormatClass(MyInputFormat.class);
        job.setOutputFormatClass(SequenceFileOutputFormat.class);

        FileInputFormat.setInputPaths(job,new Path(args[0]));
        FileOutputFormat.setOutputPath(job,new Path(args[1]));

        boolean b = job.waitForCompletion(true);
        System.exit(b ? 0 : 1);
    }
}

        自定义的InputFormat插件,改变了程序的InputFormat。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

OneTenTwo76

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值