hadoop之mapreduce详解

今天,本姑娘和大家聊一聊mapreduce。吐血整理,各位小板凳坐好啊。如有写错的话,也请多多指正。
在这里插入图片描述

首先我们了解一下什么是MapReduce。主要是由两个阶段组成。Map和Reduce。用户只需要编写map()和reduce()两个函数。即可完成简单分布式程序计算。

流程介绍:

在这里插入图片描述

①②③InputFormat

InputFormat接口决定了输入文件如何被Hadoop分块。InputFormat能够从一个job中得到split集合。然后再为这个split提供一个何时的RecordReader(getRecordReader)来读取每个split中的数据。

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(JobContext var1)负责将一个大数据再逻辑上分成许多片。然后InputSplit中记录两个参数,第一个是这个分片的起始ID,第二个是这个分片的数据大小。所以InputSplit中没有整整的存储数据,而是提供如何将数据分片的方法。
(2)createRecordReader(InputSplit var1, TaskAttemptContext var2)方法根据InputSplit定义的方法。返回一个能够读取分片记录的RecordReader。getSplits用来获取由输入文件计算出来的InputSplit。createRecordReader()提供了前面说的RecordReader的实现。将key/value对从InputSplit中正确读出,比如LineRecordReader,它以key为偏移量,Value为每行数据,使得所有的createRecordReader()返回LineRecordReader的InputFormat都是以偏移值为key,每行的数据为Value的形式读取分片。
分片:

  • minSize:InputSplit最小值配置。mapred.min.split.size默认是1M。
  • goalSize:totalSize/task个数。默认的task个数是1。
  • blockSize:HDFS中block大小。块大小默认是128M。
  • 分块的大小为:Math.max(minSize, Math.min(goalSize, blockSize))
  • 如果文件大小小于128M,则该文件不会被切片,不管文件多小都会是一个单独的切片,交给一个maptask处理.如果有大量的小文件,将导致产生大量的maptask,大大降低集群性能.
  • 有多少副本,起几个任务,切几个分片。

④进入环形缓冲区

  • 经过map函数的逻辑处理后的数据输出之后,会通过OutPutCollector收集器将数据收集到环形缓存区保存。
  • 环形缓存区的大小默认为100M,当保存的数据达到80%时,就将缓存区的数据溢出到磁盘上保存。

⑤⑥溢出(分区,排序)

  • 环形缓存区的数据达到其容量的80%时就会溢出到磁盘上进行保存,在此过程中,程序会对数据进行分区(默认HashPartition)和排序(默认根据key进行快排)
  • 缓存区不断溢出的数据形成多个小文件。第⑥步箭头的指向就是许多的小文件
  • 分区:
  • 数据从环形缓存区溢出到文件的过程中会根据用户自定义的partition函数进行分区,如果用户没有自定义该函数,程序会用默认的partitioner通过哈希函数来分区,hash partition 的好处是比较弹性,跟数据类型无关,实现简单,只需要设置reducetask的个数。分区的目的是将整个大数据块分成多个数据块,通过多个reducetask处理后,输出多个文件。通常在输出数据需要有所区分的情况下使用自定义分区,如在上述的流量统计的案例里,如果需要最后的输出数据再根据手机号码的省份分成几个文件来存储,则需要自定义partition函数,并在驱动程序里设置reduce任务数等于分区数(job.setNumReduceTasks(5);)和指明自己定义的partition(job.setPartitionerClass(ProvincePartitioner.class))。在需要获取统一的输出结果的情况下,不需要自定义partition也不用设置reducetask的数量(默认1个)。
  • 自定义的分区函数有时会导致数据倾斜的问题,即有的分区数据量极大,各个分区数据量不均匀,这会导致整个作业时间取决于处理时间最长的那个reduce,应尽量避免这种情况发生。
    - 排序:
  • 在整个mapreduce过程中涉及到多处对数据的排序,环形缓存区溢出的文件,溢出的小文件合并成大文件,reduce端多个分区数据合并成一个大的分区数据等都需要排序,而这排序规则是根据key的compareTo方法来的。
  • map端输出的数据的顺序不一定是reduce端输入数据的顺序,因为在这两者之间数据经过了排序,但reduce端输出到文件上显示的顺序就是reduce函数的写出顺序。在没有reduce函数的情况下,显示地在驱动函数里将reduce的数量设置为0(设置为0后表示没有reduce阶段,也就没有shuffle阶段,也就不会对数据进行各种排序分组),否则虽然没有reduce逻辑,但是还是会有shuffle阶段,map端处理完数据后将数据保存在文件上的顺序也不是map函数的写出顺序,而是经过shuffle分组排序过后的顺序

⑦合并(加combiner)

-这边我加了一次combiner,

  • 溢出的多个小文件各个区合并在一起(0区和0区合并成一个0区),形成大文件
  • 通过归并排序保证区内的数据有序
  • 这多个分区文件通过归并排序合并成大文件,并根据key值分好组(key值相同的,value值会以迭代器的形式组在一起)。

-combiner(map端的reduce)

  • 集群的带宽限制了mapreduce作业的数量,因此应该尽量避免map和reduce任务之间的数据传输。hadoop允许用户对map的输出数据进行处理,用户可自定义combiner函数(如同map函数和reduce函数一般),其逻辑一般和reduce函数一样,combiner的输入是map的输出,combiner的输出作为reduce的输入。很多情况下可以直接将reduce函数作为conbiner函数来使用(job.setCombinerClass(FlowCountReducer.class);)。
  • combiner属于优化方案,所以无法确定combiner函数会调用多少次,可以在环形缓存区溢出文件时调用combiner函数,也可以在溢出的小文件合并成大文件时调用combiner。但要保证不管调用几次combiner函数都不会影响最终的结果,
  • 所以不是所有处理逻辑都可以使用combiner组件,有些逻辑如果在使用了combiner函数后会改变最后rerduce的输出结果(如求几个数的平均值,就不能先用combiner求一次各个map输出结果的平均值,再求这些平均值的平均值,这将导致结果错误)。
  • combiner的意义就是对每一个maptask的输出进行局部汇总,以减小网络传输量。(原先传给reduce的数据是(a,(1,1,1,1,1,1…)),使用combiner后传给reduce的数据变为(a,(4,2,3,5…)))
  • 如果没加combiner就没有⑦步骤
  • 在这里插入图片描述

⑧分组

  • reduce的输入数据,会根据key是否相等而分为一组,如果key相等的,则这些key所对应的value值会作为一个迭代器对象传给reduce函数。reduce获得的输入数据是:第一组:(a,(1,1,1,1,1,…))第二组:(b,(1,1,1,1,1…))。对每一组数据调用一次reduce函数。

⑨合并

  • 运行reducetask的节点通过过程8,将来自多个map任务的属于自己的分区数据下载到本地磁盘工作目录。这多个分区文件通过归并排序合并成大文件,并根据key值分好组(key值相同的,value值会以迭代器的形式组在一起)。

⑩reducetask

  • reducetask从本地工作目录获取已经分好组并且排好序的数据,将数据进行reduce函数中的逻辑处理。

11.输出

  • 每个reducetask输出一个结果文件。

猜你喜欢

转载自blog.csdn.net/weixin_44695793/article/details/108166131