Hadoop 学习笔记 (四)程序优化,压缩

/ 默认分类 / 0 条评论 / 1250浏览

Hadoop 学习笔记 (四)程序优化,压缩

Map-Reduce程序性能瓶颈

很显然,硬件资源绝对是制约大数据计算效率的瓶颈,包括CPU, 内存,磁盘I/O 网络。Hadoop本身就是为了解决计算资源的问题,而出现的分布式计算框架。提高听见性能无疑是最有效的手段,但在成本有线的情况下,我们如何利用成本较低的计算资源,提高Map-Reduce离线计算的效率,如何充分利用计算资源,这在生产过程中是很重要的。

除开固定的硬件资源,一般I/O操作方面,我们还会碰到以下问题:

  1. 数据倾斜:Reducer任务分配到的数据量和计算复杂度不同,导致有的任务早早结束,一直在等待最慢的那个任务
  2. Mapper和Reducer数设置不合理:针对不同的硬件资源,是高并发处理任务,还是高性能处理任务,针对计算密集型业务和I/O密集型业务,任务数不合理,也会导致CPU或者内存利用率不充分。
  3. Mapper运行时间过长,Reducer长时间等待,造成浪费
  4. 小文件过多:由于HDFS和Map-Reduce的设计,即使再小的文件,都会为它们分配name node 的元数据空间和data node的数据空间,而且这个空间并不是弹性的,处理大量小文件会造成存储空间的部分计算资源的浪费。
  5. 不可分块的超大文件:处理过程中,如果出现不可分块的文件会造成频繁地溢写操作,频繁触发的磁盘操作和归并计算
  6. 网络传输和存储过程中数据较大,对网络I/O和磁盘I/O有较大压力

Map-Reduce程序优化方法

1. 优化数据倾斜

  1. 分区,可实现进行数据采样,了解数据分布情况,通过分区尽量将数据平均分配给各个reduce任务
  2. Map阶段之后,在不影响业务逻辑的情况下,进行区内合并,也就是combiner函数。该操作将部分计算逻辑在Mapper阶段完成,缓解了Reduce阶段的工作量,能够减缓数据倾斜现象。
  3. join操作尽量在mapper阶段完成
    • Mapper处理join操作,效率最高,一般会用DistributedCache做文件分布式缓存,适用于一张表很小,能够放在缓存里的场景
    • reducer的效率是最低的,一方面要浪费大量的网络传输,另一方面计算的复杂度将会是一个笛卡尔积,无用的重复计算太多

2. 合理设置mapper与reducer个数

这个要以业务实际情况和机器性能而定。个数增加可以减少每个任务执行的时间,但会增加调度的负担,存储的负担和网络传输的负担,需要按实际情况调整。

3. 优化map/reduce共存参数

<property>
    <name>mapreduce.job.reduce.slowstart.completedmaps</name>
    <value>0.05</value>
</property>

这个参数的含义是当Map Task完成的比例达到该值后才会为Reduce Task申请资源。当Map任务数量较多的情况下,可能存在部分map任务已经完成,而reduce任务尚未开始的情况,这个时候计算资源出现了利用不充分的情况,此时,可以提前开始申请Reduce任务资源。

4. 合并清理数据输入

  1. 合并小文件:在执行mr任务前就进行小文件合并
  2. 采用CombineTextInputFormat来作为输入,解决输入端大量小文件的场景

5. 减少溢写次数

  1. 增加环形缓冲区的默认内存和溢写阈值,默认为100MB 80%
<property>
    <name>mapreduce.task.io.sort.mb</name>
    <value>100</value>
</property>

<property>
    <name>mapreduce.map.sort.spill.percent</name>
    <value>0.80</value>
</property>

  1. 通过增大合并并行数量 ,减少合并(merge)次数
<property>
    <name>mapreduce.task.io.sort.factor</name>
    <value>10</value>
</property>
它表示当merge spill文件时,最多能有多少并行的stream向merge文件中写入。比如如果map产生的数据非常的大,产生的spill文件大于10,那么当map计算完成做merge时,就没有办法一次将所有的spill文件merge成一个,而是会分多次,每次最多10个stream。这也就是说,当map的中间结果非常大,调大mapreduce.task.io.sort.factor,有利于减少merge次数,进而减少map对磁盘的读写频率,有可能达到优化作业的目的。
  1. combiner函数就会在merge产生结果文件之前运行。因此,该操作可以减少合并(merge)次数

  2. reduce阶段,通过增加reduce读取数据缓冲内存,减少磁盘I/O开销

<property>
    <name>mapreduce.reduce.markreset.buffer.percent</name>
    <value>0.0</value>
</property>

6. 优化文件传输

  1. 使用数据压缩 很显然,压缩技术能够有效的减少底层存储(HDFS)读写字节数。压缩提高了网络带宽和磁盘空间的效率。通过压缩编码对mapper或者reducer数据传输进行数据的压缩,以减少磁盘IO。 请注意:压缩是需要大量CPU计算的操作,所以一般,运算密集型任务少用压缩,IO密集型的任务,多用压缩 下面列举了Map-Reduce中常用的几种压缩方法的对比
压缩格式是否hadoop自带结果文件后缀名是否支持切分实现类
Default.deflateorg.apache.hadoop.io.compress.DefaultCodeC
Gzip.gzorg.apache.hadoop.io.compress.GzipCodeC
bzip2.bz2org.apache.hadoop.io.compress.BZip2CodeC
LZO.lzocom.hadoop.compression.lzo.LzoCodeC
Snappy.snappyorg.apache.hadoop.io.compress.SnappyCodeC
下面是几种压缩方法的性能对比
压缩算法原始文件大小压缩文件大小压缩速度解压速度
gzip8.3GB1.8GB17,5MB/s58MB/s
bzip28.3GB1.1GB2.4MB/s9.5MB/s
LZO8.3gb2.9GB49.3MB/s74.6MB/s
简单的压缩gzip就可以,压缩率也高,速度也不慢,还是hadoop自带的很方便,就是不支持文件切分;一般生产中,磁盘吃紧,计算资源富裕的就用bzip2,磁盘富裕,计算资源吃紧的就用LZO。
使用方式,就是在驱动类中开启相关配置,注意,mapper压缩是配在`Configuration`里;reduce压缩时陪在`FileOutputFormat`里:
//开启map端的输出压缩
conf.setBoolean("mapreduce.map.output.compress", true);
//设置压缩方式
conf.setClass("mapreduce.map.output.compress.codec", BZip2Codec.class, CompressionCodec.class);

//开启reduce端的输出压缩
FileOutputFormat.setCompressOutput(job, true);
//设置压缩方式
FileOutputFormat.setOutputCompressorClass(job, BZip2Codec.class);
  1. 使用sequenceFile。相比于TextFile,sequenceFile的内容字节更为紧凑,支持基于记录(Record)或块(Block)的数据压缩;但使用时需要一个合并文件的过程,且合并后的文件不方便查看。

7. 其他参数调优

可通过调整配个任务可使用的计算在原调整优化


<!-- map核心数设置:-->
<property>
    <name>mapreduce.map.cpu.vcores</name>
    <value>1</value>
</property>

<!-- reduce核心数设置:-->
<property>
    <name>mapreduce.reduce.cpu.vcores</name>
    <value>1</value>
</property>

<!-- maptask内存设置:-->
<property>
    <name>mapreduce.map.memory.mb</name>
    <value>1024</value>
</property>

<!-- reducetask内存设置:-->
<property>
    <name>mapreduce.reduce.memory.mb</name>
    <value>1024</value>
</property>

<!-- reduce去map端拿数据并行度 -->
<property>
    <name>mapreduce.reduce.shuffle.parallelcopies</name>
    <value>5</value>
</property>