Hadoop数据压缩

            Hadoop数据压缩

                               作者:尹正杰

版权声明:原创作品,谢绝转载!否则将追究法律责任。

 

 

 

一.Hadoop压缩概述

1>.压缩数据的基本原则

  压缩技术能够有效减少Hadoop底层存储系统(HDFS集群)读写字节数。压缩提高了网络带宽和磁盘空间的效率。在运行MR程序时,I/O操作,网络数据传输,Shuffle和Merge要花费大量的时间,尤其是数据规模很大和工作负载密集的情况下,因此,使用数据压缩显得非常重要。

  鉴于磁盘I/O和网络带宽时Hadoop的宝贵资源,数据压缩对于节省资源,最小化磁盘I/O和网络传输非常有帮助。可以在任意MapReduce阶段启用压缩。不过,尽管压缩和解压操作的CPU开销不高,其性能的提升和资源的节省并非没有代价。

  压缩策略:
    综上所述,压缩时提高Hadoop运行效率的一种优化策略。
    通过对Mapper,Reducer运行过程的数据进行压缩,以减少磁盘IO,提高MR程序运行速度。
    温馨提示:采用压缩技术减少来啦磁盘IO,但同时增加了CPU运算负担。所以,压缩特性运用得当能提高性能,但运用不当也可能降低性能。

  压缩的基本原则:
    (1)运算密集型的job,少用压缩;
    (2)IO密集型的job,多用压缩;

2>.MapReduce支持的压缩/解压缩算法

  DEFLATE:
    是Hadoop自带的压缩格式;
    采用DEFLATE压缩算法;
    文件的扩展名通常以".deflate"结尾;
    不支持切分;
    换成该压缩格式后,和文本一样,不需要修改原来的程序代码;
    对应压缩编码/解码器:org.apache.hadoop.io.compress.DefaultCodec
  Gzip:
    是Hadoop自带的压缩格式;
    采用DEFLATE压缩算法;
    文件的扩展名通常以".gz"结尾;
    不支持切分;
    换成该压缩格式后,和文本一样,不需要修改原来的程序代码;       对应压缩编码/解码器:org.apache.hadoop.io.compress.GzipCodec
  bzip2:     是Hadoop自带的压缩格式;
    采用bzip2压缩算法;
    文件的扩展名通常以".bz2"结尾;
    支持切分;
    换成该压缩格式后,和文本一样,不需要修改原来的程序代码;
    对应编码/解码器:org.apache.hadoop.io.compress.BZip2Codec
  LZO:     不是Hadoop自带的压缩格式,需要手动安装;
    采用LZO压缩算法;
    文件的扩展名通常以".lzo"结尾;  
    支持切分;
    换成压缩格式后,需要建索引,还需要指定输入格式;
    对应编码/解码器:com.hadoop.compression.lzo.LzopCodec
  Snappy:
    不是Hadoop自带的压缩格式,需要手动安装;
    采用Snappy压缩算法;
    文件的扩展模通常以".snappy"结尾;  
    不支持切分;
    换成该压缩格式后,和文本一样,不需要修改原理来的程序代码;
    对应编码解码器:org.apache.hadoop.io.compress.SnappyCodec
    

3>.压缩位置选择

  压缩可以再MapReduce作用的任意阶段启用。我们通常考虑数据压缩的位置再输入端采用压缩,Mapper输出采用压缩和Reducer输出采用压缩。

  输入端采用压缩:
    (1)在有大量数据并计划重复处理的情况下,应该考虑对输入进行压缩;
    (2)无需显示指定使用的编解码方式,Hadoop自动检查文件扩展名,如果扩展名能够匹配,就会用恰当的编解码方式对文件进行压缩和解压缩,否则,Hadoop就不会使用任何编解码器。

  Mapper输出采用压缩:
    (1)当Map任务输出的中间数据量很大时,应考虑在此阶段采用压缩技术。这能显著改善内部数据Shuffle过程,而Shuffle过程在Hadoop处理过程中是资源消耗最多的环节;
    (2)如果发现数据量大造成网络传输缓慢,应该考虑使用压缩技术;
    (3)可用于压缩Mapper输出的快速编解码器包括Lzo和Snappy。
            
  Reducer输出采用压缩:
    (1)此阶段启用压缩技术能够减少要存储的数据量,因此降低所需的磁盘空间;
    (2)当MapReduce作业形成作业链条时,因为第二个作业输入也已压缩,所以启用压缩同样有效。


  温馨提示:
    (1)LZO是供Hadoop压缩数据用的通用压缩编解码器,器设计目标是达到与硬盘速度相当的压缩速度,因此压缩速度是有限考虑的因此,而不是压缩率;
    (2)与Gzip编解码器相比,它的压缩速度是Gzip的5倍,而解压速度是Gzip的2倍。同一个文件用于LZO压缩后比用Gzip压缩后大50%左右,但比压缩前小25%~50%,这对改善性能非常有利,Map阶段完时间快4倍。

 

二.压缩方式选择

1>.Gzip压缩

  优点:
    (1)压缩率比较高,而且压缩/解压速度也比较快;
    (2)Hadoop本身支持,再应用中处理Gzip格式的文本和直接处理文本一样;
    (3)大部分Linux系统都自带Gzip命令,使用方便;   缺点:
    不支持切分(Split)

  应用场景:
    当每个文件压缩之后再一个块大小(默认128MB,可适当调大)以内的,都可以考虑使用Gzip压缩格式。
    例如:一天或者一小时的日志压缩成Gzip文件。

2>.Bzip2压缩

  优点:
    (1)支持切分(Split);
    (2)具有很高的压缩率;
    (3)比Gzip压缩率都高;
    (4)Hadoop本身就带,使用方便;
  缺点:     压缩/解压速度慢。   应用场景:
    (1)适合对速度要求不高,但需求较高压缩率的适合;
    (2)输出之后的数据比较大,处理之后的数据需要压缩存档减少磁盘空间并且以后数据用的比较少的情况;
    (3)对单个很大的文本文件想压缩减少存储空间,同时又需要支持Split,而且兼容之前的应用程序的情况;

3>.Lzo压缩

  优点:
    (1)压缩/解压速度也比较快,合理的压缩率;
    (2)支持切分(Split);
    (3)Hadoop中最流行的压缩格式;
    (4)可以在Linux系统下安装lzop命令,使用方便;
  缺点:     (1)压缩率比Gzip要低;
    (2)Hadoop本身不支持,需要安装;
    (3)再应用中对Lzo格式的文件需要做一些特殊处理(为了支持Split需要建索引,还需要指定InputFormat为Lzo格式);   应用场景:
    一个很大的文本文件,压缩格之后海达裕200M以上的可以考虑,而且单个文件越大,Lzo优点越明显。

4>.Snappy压缩

  优点:
    高速压缩速度和合理的压缩率。

  缺点:
    (1)不支持切分(Split);
    (2)压缩比例要比Gzip低;
    (3)Hadoop本身不支持,需要安装。
  应用场景:
    (1)当MapReduce作业的Map输出的数据比较大的时候,作为Map到Reduce的中间数据压缩格式;
    (2)作为一个MapReduce作业的输出和另外一个MapReduce作业的输入;

5>.压缩性能的比较

  以下是网友对Hadoop的数据进行压缩测试结果。从结果上,我们选择压缩格式最主要考虑的因素应该是压缩性能,如果一个压缩算法速度太慢会大大降低工作效率。

  据说i7处理器64位操作系统中,使用Snappy压缩速度可达250MB/s,解压速度可达500MB/s,关于Snappy压缩算法说明可参考官网(http://google.github.io/snappy/)

  生产环境中建议大家有限考虑LZO和Snappy压缩算法,其次再考虑gzip算法。

 

三.压缩参数配置说明

  输入压缩阶段:  
    参数名称:
      io.compression.codecs(在core-site.xml中配置):     默认值:
      org.apache.hadoop.io.compress.DefaultCodec
      org.apache.hadoop.io.compress.GzipCodec
      org.apache.hadoop.io.compress.BZip2Codec     说明:       Hadoop使用文件扩展名判断是否支持某种编解码器。

  mapper输出阶段:
    参数名称:
      mapreduce.map.output.compress(在mapred-site.xml中配置):
      默认值:
        false
      说明:
        这个参数为true时启用压缩。
    参数名称:
      mapreduce.map.output.compress.codec(在mapred-site.xml中配置):
      默认值:
        org.apache.hadoop.io.compress.DefaultCodec
      说明:
        企业多使用LZO或Snappy编解码器在此阶段压缩数据。

  reduce输出阶段: 
    参数名称:   
      mapreduce.output.fileoutputformat.compress(在mapred-site.xml中配置):
      默认值:
        false
      说明:
        这个参数设为true启用压缩
    参数名称:
      mapreduce.output.fileoutputformat.compress.codec(在mapred-site.xml中配置):
      默认值:
        org.apache.hadoop.io.compress. DefaultCodec
      说明:
        使用标准工具或者编解码器,如gzip和bzip2。
    参数名称:
      mapreduce.output.fileoutputformat.compress.type(在mapred-site.xml中配置):
      默认值:
        RECORD
      说明:  
        SequenceFile输出使用的压缩类型:NONE和BLOCK

 

四.数据流的压缩和解压缩案例

package cn.org.yinzhengjie.compress;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.compress.*;
import org.apache.hadoop.util.ReflectionUtils;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class TestCompress {

    public static void main(String[] args) throws IOException {
        compress("E:\\yinzhengjie\\compress\\input\\web.log",BZip2Codec.class);
        decompress("E:\\yinzhengjie\\compress\\input\\web.log.bz2");
    }

    private static void decompress(String path) throws IOException {
        //创建一个压缩格式工厂(这是官方提供的工厂设计模式)
        CompressionCodecFactory factory = new CompressionCodecFactory(new Configuration());

        //通过factory获取压缩格式
        CompressionCodec codec = factory.getCodec(new Path(path));

        //创建输入流
        FileInputStream fis = new FileInputStream(path);

        //从输入流读取数据并解压缩,从而从底层的流读取未压缩(解压)的数据
        CompressionInputStream cis = codec.createInputStream(fis);


        //创建输出流,"path.substring(0, path.length() - 4)"表示去掉扩展名".bz2"
        FileOutputStream fos = new FileOutputStream(path.substring(0, path.length() - 4) + "-bak");

        //流拷贝
        IOUtils.copyBytes(cis,fos,1024);

        //关闭资源
        IOUtils.closeStream(cis);
        IOUtils.closeStream(fos);
    }

    private static void compress(String path, Class<? extends CompressionCodec> codecClass) throws IOException {
        //创建输入文件的输入流
        FileInputStream fis = new FileInputStream(path);

        //通过Hadoop官方提供的ReflectionUtils工具类创建压缩格式(该压缩格式是用户传递进来的)的实体对象(底层使用的时反射技术)
        CompressionCodec codec = ReflectionUtils.newInstance(codecClass, new Configuration());

        //创建输出流,codec.getDefaultExtension()是默认的扩展名
        FileOutputStream fos = new FileOutputStream(path + codec.getDefaultExtension());

        //对输出流的数据进行压缩,即将fos以压缩格式写入底层的包装流,
        CompressionOutputStream cos = codec.createOutputStream(fos);

        //流拷贝
        IOUtils.copyBytes(fis,cos,1024);

        //关闭资源
        IOUtils.closeStream(fis);
        IOUtils.closeStream(cos);
    }
}

 

五.压缩案例实操

1>.测试数据

  博主推荐阅读:
    https://www.cnblogs.com/yinzhengjie2020/p/12815355.html

2>.Mapper输出端采用压缩案例代码

package cn.org.yinzhengjie.compress;

import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

import java.io.IOException;

public class ETLMapper extends Mapper<LongWritable,Text,Text,NullWritable> {

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        String[] fields = value.toString().split(" ");

        //除去日志中字段长度小于等于11的日志。
        if (fields.length >  11){
            context.write(value,NullWritable.get());

            //使用计数器
            context.getCounter("ETL","True").increment(1);
        }else {
            context.getCounter("ETL","False").increment(1);
        }
    }
}
ETLMapper.java
package cn.org.yinzhengjie.compress;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.compress.BZip2Codec;
import org.apache.hadoop.io.compress.CompressionCodec;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

import java.io.IOException;

public class ETLDriver {

    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException, IOException {

        //创建一个配置文件对象
        Configuration conf = new Configuration();

        // 开启map端输出压缩
        conf.setBoolean("mapreduce.map.output.compress", true);

        // 设置map端输出压缩方式
        conf.setClass("mapreduce.map.output.compress.codec", BZip2Codec.class, CompressionCodec.class);

        //获取一个Job实例
        Job job = Job.getInstance(conf);

        //设置我们的当前Driver类路径(classpath)
        job.setJarByClass(ETLDriver.class);

        //设置自定义的Mapper类路径(classpath)
        job.setMapperClass(ETLMapper.class);

        //设置自定义的Mapper程序的输出类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(NullWritable.class);

        //设置自定义的Reducer程序的输出类型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(NullWritable.class);


        //设置输入数据
        FileInputFormat.setInputPaths(job,new Path(args[0]));

        //设置输出数据
        FileOutputFormat.setOutputPath(job,new Path(args[1]));

        //提交我们的Job,返回结果是一个布尔值
        boolean result = job.waitForCompletion(true);

        //如果程序运行成功就打印"Task executed successfully!!!"
        if(result){
            System.out.println("Task executed successfully!!!");
        }else {
            System.out.println("Task execution failed...");
        }

        //如果程序是正常运行就返回0,否则就返回1
        System.exit(result ? 0 : 1);
    }
}
ETLDriver.java

3.Reducer输出端采用压缩案例代码

package cn.org.yinzhengjie.compress;

import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

import java.io.IOException;

public class ETLMapper extends Mapper<LongWritable,Text,Text,NullWritable> {

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        String[] fields = value.toString().split(" ");

        //除去日志中字段长度小于等于11的日志。
        if (fields.length >  11){
            context.write(value,NullWritable.get());

            //使用计数器
            context.getCounter("ETL","True").increment(1);
        }else {
            context.getCounter("ETL","False").increment(1);
        }
    }
}
ETLMapper.java
package cn.org.yinzhengjie.compress;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.compress.BZip2Codec;
import org.apache.hadoop.io.compress.CompressionCodec;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

import java.io.IOException;

public class ETLDriver {

    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException, IOException {

        //创建一个配置文件对象
        Configuration conf = new Configuration();

        //获取一个Job实例
        Job job = Job.getInstance(conf);

        //设置我们的当前Driver类路径(classpath)
        job.setJarByClass(ETLDriver.class);

        //设置自定义的Mapper类路径(classpath)
        job.setMapperClass(ETLMapper.class);

        //设置自定义的Mapper程序的输出类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(NullWritable.class);

        //设置自定义的Reducer程序的输出类型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(NullWritable.class);


        //设置输入数据
        FileInputFormat.setInputPaths(job,new Path(args[0]));

        //设置输出数据
        FileOutputFormat.setOutputPath(job,new Path(args[1]));

        // 设置reduce端输出压缩开启
        FileOutputFormat.setCompressOutput(job, true);

        // 设置reduce端输出压缩的方式
        FileOutputFormat.setOutputCompressorClass(job, BZip2Codec.class);

        //提交我们的Job,返回结果是一个布尔值
        boolean result = job.waitForCompletion(true);

        //如果程序运行成功就打印"Task executed successfully!!!"
        if(result){
            System.out.println("Task executed successfully!!!");
        }else {
            System.out.println("Task execution failed...");
        }

        //如果程序是正常运行就返回0,否则就返回1
        System.exit(result ? 0 : 1);
    }
}
ETLDriver.java

 

posted @ 2020-05-04 21:13  JasonYin2020  阅读(273)  评论(0编辑  收藏  举报