MapReduce知识点整理

MapReduce知识点整理

基于版本:Hadoop 2.7.2

序列化

Q: 为什么Hadoop不使用Java自带的序列化?
A: Java自带的序列化框架过于重量级(附带很多额外信息:校验信息、Header、继承体系等),网络传输效率低,所以Hadoop自己实现了序列化机制(Writable接口).

自定义可序列化类

  1. 实现Writable接口
  2. 反序列化用到反射,需要调用无参数构造方法,因此自定义的可序列化类需要有无参数构造方法
  3. 重新write方法(序列化)和readFields方法(反序列化),注意序列化和反序列化时字段顺序需要保持一致
  4. Hadoop的shuffle过程会对键排序,因此如果需要自定义的可序列化类对象作为键,还需要实现WritableComparable接口
  5. 如果对象需要作为reduce的输出写入文件,需要重写toString方法实现其字符串形式的表示,一般采用\t将字段分开的形式,便于之后的MapReduce任务读取

InputFormat

InputFormat

抽象类InputFormat是输入格式的基类,主要实现了两个功能:

  • 将输入文件切片
  • 从切分读入键值对序列

FileInputFormat

  • FileInputFormat是针对文件输入的抽象类,主要处理文件切片逻辑
  • 按输入文件长度切片,默认的切片大小等于BlockSize,对于每个输入文件单独切分而不是合并切片
  • splitSize = Math.max(minSize, Math.min(maxSize, blockSize))
  • 通过设置mapreduce.input.fileinputformat.split.minsize(默认值为1)大于BlockSize,让切片变大
  • 通过设置mapreduce.input.fileinputformat.split.maxsize(默认值为Long.MAX_VALUE)小于BlockSize,让切片变小
  • 细节:将文件切片时,每次判断剩余文件大小是否大于切片大小的1.1倍,如果是,则新建切片

TextInputFormat

  • 默认的输入格式实现类
  • 按行读取文件
  • 键:存储在整个文件中的起始字节偏移量,LongWritable类型
  • 值:本行内容,不包含换行符和回车符,Text类型

CombineTextInputFormat

  • 功能:合并小文件
  • 两阶段切片机制:(1)虚拟存储过程;(2)切片过程
  • CombineTextInputFormat.setMaxInputSplitSize(job, MAX_SIZE)将虚拟存储切片最大的大小设置为MAX_SIZE
  • CombineTextInputFormat.setMaxInputSplitSize(job, MIN_SIZE)将虚拟存储切片最小的大小设置为MIN_SIZE

OutputFormat

抽象类OutputFormat是Reduce输出的基类

TextOutputFormat

默认的输出格式,把每行记录作为文本行。内部调用toString方法把键和值转化为字符串

SequenceFileOutputFormat

当该MapReduce任务的输出被用于下一个MapReduce任务的输入时使用,下一个MapReduce任务使用SequenceFileInputFormat作为输入格式。优点是合并了小文件,存储格式紧凑,便于压缩。

自定义OutputFormat

  • 为了控制输出文件的路径和输出格式,可以自定义OutputFormat. 例如可以用于MapReduce任务结果写入MySQL, redis等存储中
  • 自定义一个类继承FileOutputFormat
  • 自定义一个类继承RecordWriter,重写write方法

MapReduce工作流程

MapTask工作机制

  1. Read阶段
  2. Map阶段
  3. Collect阶段
    • 把计算好的<k, v>对以及元信息放在环形缓冲区,所谓环形缓冲区实际只是一个字节数组,用equator标明了环的起始位置
    • <k, v>对从equator按照索引递增方向存储
    • 元信息(partition, keystart, valstart, vallen, kvindex)从equator按照索引递减方向存储
  4. 溢写阶段:当环形缓冲区的内容占缓冲区大小80%时,会把环形缓冲区的内容快速排序后写入磁盘临时文件(如果有combiner,则写入磁盘前会先经过combiner),当MapTask的所有数据处理完成后,对临时文件中的内容进行归并,排序后的内容写入磁盘(如果有combiner,则写入磁盘前会再次经过combiner)
    • Q: 为什么不等环形缓冲区满再溢写? A: 会出现写入环形缓冲区阻塞
    • Q: 为什么把<k, v>对与元信息放在同一个buffer? A: 提高buffer利用率,减少溢写次数

关于MapTask的源码级别的分析可以参考这篇博客:MapReduce源码解析–Map解析

ReduceTask工作机制

  1. Copy阶段
  2. Sort阶段
  3. Reduce阶段

关于ReduceTask的源码级别的分析可以参考这篇博客:MapReduce源码解析–Reduce解析

Map/Reduce任务的并行度

  • map任务并行度等于切片数,默认切片大小等于BlockSize
  • reduce任务并行度由用户指定参数numReduceTasks决定,等于输出文件的个数
  • reduce任务的并行度默认为1
  • 如果不需要reduce任务,则将numReduceTasks设为0,此时map任务的输出直接写入输出路径
  • 如果自定义分区数大于1而numReduceTasks为1,则不会进行分区

Partition

  • 默认:HashPartioner
public class HashPartitioner<K, V> extends Partitioner<K, V> {
    public int getPartition(K key, V value, int numReduceTAsks) {
        return (key.hashCode() & Integer.MAX_VALUE) & numReduceTasks;
    }
}
  • 自定义Partitioner的时候,注意同步修改numReduceTasks参数,与自定义的分区数保持一致。

排序

MapReduce工作流程排序机制

  • 不管程序逻辑是否需要,MapReduce框架的map阶段和reduce阶段都会进行排序,默认情况下按照字典序排序
  • 对MapTask, 它会将处理的结果暂时放到内存中的环形缓冲区,当环形缓冲区的使用率达到80%的阈值后,再对缓冲区的数据进行快速排序,并将排序后的数据溢写到磁盘上,当MapTask所有数据处理完毕后,它会将磁盘上所有文件归并,归并过程使用是堆排序(优先队列实现)
  • 对ReduceTask, 它从每个MapTask上远程拷贝相应数据文件,如果文件大小超过一定阈值,则溢写到磁盘,否则存储在内存中。如果磁盘上文件数据达到或超过阈值,则进行一次归并合并成一个更大的文件;如果内存中文件大小或者数目超过一定阈值,则进行一次合并后将数据溢写到磁盘上。当所有数据拷贝完毕后,ReduceTask统一对内存和磁盘上的所有数据进行一次归并. 和MapTask一样,归并过程使用的是堆排序

MapReduce排序应用:全排序

单分区

MapReduce框架自动实现输出结果的全排序,缺点:效率低

分区排序

自定义Partitioner,按照需要排序的键分区,不同的键按照顺序进入不同的分区

采样分区

在数据分布不均匀的情况下,按照等距间隔划分Partitioner可能造成数据倾斜,拖慢整个任务的执行速度. 使用InputSampler对数据进行采样,将得到的数据采样分区间隔写入文件,使用TotalOrderPartitioner类读取分隔值作为分区依据

MapReduce排序应用:group逻辑

在MapReduce里实现类似SQL的groupby的逻辑,可以在自定义map输出键类型的基础上继承GroupingComparator实现让部分字段相同的键进入同一个reduce方法

Join in MapReduce

MapReduce实现SQL的join逻辑,有两种方法,一种是Reduce侧join,一种是Map侧join

Reduce侧join

在map里给记录增加一个标签字段,通过获取切片的路径,获取每条记录来自哪一个输入文件/表的信息,给不同输入文件/表的输入打不同标签. map的输出以连接的字段(join on)为键,其余输入字段和新增的标签字段为值,连接字段相同、来源文件/表不同的记录会在同一个partition中给同一个reduce处理,reduce中将它们连接起来。
reduce侧join适合两表或多表较大,无法放入节点内存的情形,开销较大。

Map侧join

把小表/文件分发到map节点上,在mapper的setup阶段,将小表/文件读入内存,在map中直接和大表join
map侧join适合需要连接的表只有一张表很大,其余表都很小,可以全部放入节点内存的情形,开销小。

调优

输入阶段

输入大量小文件,造成切片过小,map实际运行时间很短。调优方法:

  • CombineTextInputFormat合并大量小文件
  • 开启JVM重用:一个map运行在一个JVM上,开启JVM重用,该map运行完毕后,JVM继续运行其他map. 通过设置mapreduce.job.jvm.numtasks值实现

map阶段

  • 减少溢写次数:调整io.sort.mb(默认100M)以及sort.spill.percent(默认80%)增大环形缓冲区以及环形缓冲区发生溢写的阈值
  • 减少merge次数:调整io.sort.factor参数,增大merge的文件数
  • 业务逻辑允许的前提下,使用Combiner减少shuffle阶段的IO

reduce阶段

  • 合理设置reduce并行度
  • 设置map/reduce共存:调整slowstart.completedmaps,使map运行到一定程度后reduce就开始运行
  • 设置mapred.job.reduce.input.buffer.percent(默认为0.0),将一定比例的shuffle后的buffer不落盘,直接给reduce任务

IO传输

  • 使用Snappy/LZO压缩方式传输map输出
  • 使用SequenceFile二进制文件在多个MapReduce任务之间传输

数据倾斜

减小数据倾斜的方法:

  • 抽样和范围分区:对原始数据集抽样预设分区边界
  • 自定义分区
  • 使用Combiner
  • 使用map join替代reduce join
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章