MapReduce原理讲解(带源码)

在MapReduce 中运行的job 类里,最先出现的就是FileInputFormat 类,它继承于InputFormat 。

一、InputFormat:

这是一个重要的抽象类,其中包括两个抽象方法:

//源码:
public abstract class InputFormat<K, V> {
    public InputFormat() {
    }

    public abstract List<InputSplit> getSplits(JobContext var1) throws IOException, InterruptedException;

    public abstract RecordReader<K, V> createRecordReader(InputSplit var1, TaskAttemptContext var2) throws IOException, InterruptedException;
}

1.getSplits

用于分割文件,返回List集合,集合中类型为InputSplit 包装类,其中包括长度、位置等属性。

//源码:
public abstract class InputSplit {
    public InputSplit() {
    }

    public abstract long getLength() throws IOException, InterruptedException;

    public abstract String[] getLocations() throws IOException, InterruptedException;

    @Evolving
    public SplitLocationInfo[] getLocationInfo() throws IOException {
        return null;
    }
}

由于大数据非常“大”,Hadoop 将数据分成128MB的Block块,MapReduce将文件按分隔符(空格、回车或结束符)切割开,放入差不多这个大小的块里,如果放不下就再次切割,然后将块储存在不同节点上。于是为了正常读取,就需要每个块储存节点的位置,块的顺序位置,如果按分隔符切割的切片很大,存放在了不止一个块中,还要先找到完整的切片,组成逻辑块,而如果这些长度、定位信息以属性的形式存在InputSplit类里。

2.createRecordReader

这是一个读取器,作用就是按行读字节流。

二、两个方法的重写

FileInputFormat 继承了InputFormat ,重写了上面的getSplits 方法

1.重写中的getSplits 方法

1.1使用Stopwatch 线程监控,用心跳消息防止文件丢失;

1.2在重写方法中规定最小切割字节

——算数函数取两数最大值:①1字节(1L),②根据任务设置最小切割字节。设置方法:

//写在Job类中
Configuration con = new Configuration();
con.set("mapreduce.input.fileinputformat.split.minsize","1024");
Job job = Job.getInstance(con);//最小分割值设为1024

1.3还规定了最大分割字节

——非常大的一个数。

1.4循环获取位置并记录。

判断文件类型,如果为本地文件(file),就记录本地块位置;如果是读远程文件(hdfs),就获得远程块位置并记录。

1.5获得切割尺寸

——防止超越之前预设的(或默认的)最小值和最大值——取最小切割字节、块尺寸、最大切割字节中中间大小的那个。

1.6进行切割

进入循环比较,看切割尺寸splitSize 和文件长度bytesRemaining 比谁大。(1.1是因为能够容忍稍微超过分割尺寸一点点)

如果文件长度超过了一次切割的长度,那么进入循环,将文件进行分割;

如果文件长度不超过切割尺寸,那边么不进循环,不进行分割。

//至此,已经分好逻辑块了。

2.重写中的createRecordReader 方法

实际上TextInputFormat 类继承了上述FileInputFormat 抽象类,不过框架中new FileInputFormat() 就能调到TextInputFormat。

而createRecordReader 是在TextInputFormat 中被重写的。

//源码:
public class TextInputFormat extends FileInputFormat<LongWritable, Text> {
    public TextInputFormat() {
    }

    public RecordReader<LongWritable, Text> createRecordReader(InputSplit split, TaskAttemptContext context) {
        String delimiter = context.getConfiguration().get("textinputformat.record.delimiter");
        byte[] recordDelimiterBytes = null;
        if (null != delimiter) {
            recordDelimiterBytes = delimiter.getBytes(Charsets.UTF_8);
        }

        return new LineRecordReader(recordDelimiterBytes);
    }

    protected boolean isSplitable(JobContext context, Path file) {
        CompressionCodec codec = (new CompressionCodecFactory(context.getConfiguration())).getCodec(file);
        return null == codec ? true : codec instanceof SplittableCompressionCodec;
    }
}

2.1这个方法返回一个LineRecordReader 类,能够按行读取。

2.2LineRecordReader

这个类中初始化设置每行最大长度2GB左右。然后用FileSystem 读文件。

类中的属性inSplitLineReader 类。

进入源码会发现,实际上它用的是InputStream 字节流读取,

但众所周知,按行读取readLine() 方法是缓冲字符流BufferedReader 的方法,这里使用字节流是没有这个方法的,所以这里的父类LineReader 是自己单独写了个按行读取:

 

自己设缓冲区大小(默认64KB,如果超长可以改),先在这个缓冲区内找换行符"\n"(ascII码=10),然后按缓冲字节数组的大小读入数据。


至此,我们已经得到了文本的逻辑切片。


三、Mapper

1.还记得我们写的mapper吗

操作:写一个继承Mapper 的类,有两个传入值、两个输出值。

经过上面的步骤按行读取,会形成两个值——(k,v)。这两个值经过我们自己写的继承Mapper 的类中的map 方法,会再传出两个新的值(k,v),通过context.write 输出到OutputCollector。两个传入值、两个输出值,这就是我们在map 方法里看到的4个参数。

2.环形缓冲区

上面的InputFormat 的工作相当于把牛肉切成牛排,但要吃的话,还需要咬成小块一口口吃

——为了保证后面每一次处理的数据大概都是一个固定值,这里采用环状数组作为缓冲区,OutputCollector 把收集到的(k,v)放到环形缓冲区,缓冲区默认大小为100MB,但只使用80%,多了就会触发spill溢出(这个过程是对数据进行真正处理之前的一道工序,数据在缓冲区每存好一组就拿去处理一组)

        为什么用环状数组?

        (环状数组使用头尾两个指针移动来判断是在填充数据还是在释放数据,两指针相遇就知道数组满了。相比于普通数组,只需要一个for循环就能解决填充和释放两件事)

        1.环状数组下标永不越界;

        2.空间利用率很高,少个for,只要移动指针就知道是在填充数据还是释放数据。

四、Shuffle

shuffle 是一个过程,每一个步骤分散在map task 和 reduce task 节点上。

设立描述的Shuffle 过程是上面提到的spill 溢出过程。

整体来看,分3个步骤:

1.分区

使用Hash分区,根据Hash值将不同的数据分区。

排序前先给上面缓冲区中的每一对(k,v) hash 一个分区值,相同值的数据分在同一区中。

2.排序

这里用的是快速排序。

将缓冲区中的数据根据分区和key 进行排序,使区内有序。

3.合并

通过spill溢出,不断将分好区排好序的数据溢出到本地磁盘文件,如果map阶段处理的数据量太大,将会溢出成多个文件。

合并使用Combiner 进行局部value ,先合并成小文件,再用归并排序,按区合并成大文件。无论小文件还是大文件,都是区内有序的。

五、Reducer

reduce task 根据自己的分区号,去各个map task 节点上拷贝相同hash分区的数据到reduce task本地磁盘工作目录(这个过程依旧属于Shuffle);

reduce task 会把同一分区来自不同map task 的结果文件再进行归并排序,合并成一个大文件(所以有几个reduce 就分几个区,可以自己设置)

//设置reducer个数
job.setNumReduceTasks(2);

到合并成分区大文件为止,shuffle 过程结束。

最后再使用OutputFormat 将结果写入part-r-000**结果文件.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Woovong

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

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

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

打赏作者

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

抵扣说明:

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

余额充值