• Hadoop编码解码【压缩解压缩】机制具体解释(1)


    想想一下,当你须要处理500TB的数据的时候,你最先要做的是存储下来。

    你是选择源文件存储呢?还是处理压缩再存储?非常显然,压缩编码处理是必须的。一段刚刚捕获的60分钟原始视屏可能达到2G,经过压缩处理能够减至500MB左右。一张单反照片可能有5MB。经过压缩之后仅仅有400KB,而质量不会发生明显的损失。


    hadoop面临的情况也是一样的,大量的数据须要存储在磁盘或者内存中,进行压缩是一种经济的方法。对数据文件进行压缩,能够有效降低存储文件所需的空间,并加快数据在网络上或者到磁盘上的传输速度。

    在Hadoop中。压缩应用于文件存储、Map阶段到Reduce阶段的数据交换(须要打开相关的选项)等情景。

    数据压缩的方式许多。不同特点的数据有不同的数据压缩方式:如对声音和图像等特殊数据的压缩,就能够採用有损的压缩方法,同意压缩过程中损失一定的信 息,换取比較大的压缩比。而对音乐数据的压缩,因为数据有自己比較特殊的编码方式,因此也能够採用一些针对这些特殊编码的专用数据压缩算法。


    hadoop使用的压缩工具主要有:


    压缩格式 工具 算法 扩展名 多文件 可切割性
    DEFLATE DEFLATE .deflate
    GZIP gzip DEFLATE .gzp
    ZIP zip DEFLATE .zip 是,在文件范围内
    BZIP2 bzip2 BZIP2 .bz2
    LZO lzop LZO .lzo


    hadoop使用的编码器:



    考察压缩工具的性能,主要从时间和空间考虑。一个压缩工具,压缩相同的文件。花费的时间越短。空间越少。压缩率越大,我们就会越喜欢他。

    另外还要考虑的就是可不可以切割文件。在hadoop中不可以切割文件是个不好的消息。

    由于hadoop处理数据进行计算的时候,须要将大量的大文件拆分,切割就非常重要了。


    哪个性能好呢?(以下表格数据引自IBM

    压缩算法 原始文件大小 压缩文件大小 压缩速度 解压速度
    gzip 8.3GB 1.8GB 17.5MB/s 58MB/s
    bzip2 8.3GB 1.1GB 2.4MB/s 9.5MB/s
    LZO-bset 8.3GB 2GB 4MB/s 60.6MB/s
    LZO 8.3GB 2.9GB 49.3MB/s 74.6MB/s

    这还不是所有。hadoop通过压缩流,也就是将文件写进压缩流里面进行数据读写,性能怎样呢?



        下面引自xuxm2007
    CompressionCodec对流进行压缩和解压缩
    CompressionCodec有两个方法能够用于轻松地压缩或解压缩数据。

    要想对正在被写入一个输出流的数据进行压缩。我们能够使用 createOutputStream(OutputStreamout)方法创建一个CompressionOutputStream(未压缩的数据将 被写到此),将其以压缩格式写入底层的流。相反。要想对从输入流读取而来的数据进行解压缩,则调用 createInputStream(InputStreamin)函数,从而获得一个CompressionInputStream,。从而从底层的流 读取未压缩的数据。CompressionOutputStream和CompressionInputStream类似干 java.util.zip.DeflaterOutputStream和java.util.zip.DeflaterInputStream,前两者 还能够提供重置其底层压缩和解压缩功能,当把数据流中的section压缩为单独的块时,这比較重要。比方SequenceFile。

    下例中说明了怎样使用API来压缩从标谁输入读取的数据及怎样将它写到标准输出:


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class StreamCompressor 
    {
         public static void main(String[] args) throws Exception 
         {
              String codecClassname = args[0];
              Class<?> codecClass = Class.forName(codecClassname); // 通过名称找相应的编码/解码器
              Configuration conf = new Configuration();
    CompressionCodec codec = (CompressionCodec) ReflectionUtils.newInstance(codecClass, conf);
     // 通过编码/解码器创建相应的输出流
              CompressionOutputStream out = codec.createOutputStream(System.out);
     // 压缩
              IOUtils.copyBytes(System.in, out, 4096, false);
              out.finish();
         }


    用CompressionCodecFactory方法来判断CompressionCodecs

        

    在阅读一个压缩文件时,我们通常能够从其扩展名来判断出它的编码/解码器。以.gz结尾的文件能够用GzipCodec来阅读。如此类推。每一个压缩格式的扩展名如第一个表格;

    CompressionCodecFactory提供了getCodec()方法。从而将文件扩展名映射到对应的CompressionCodec。

    此方法接受一个Path对象。以下的样例显示了一个应用程序,此程序便使用这个功能来解压缩文件。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    public class FileDecompressor {
        public static void main(String[] args) throws Exception {
           String uri = args[0];
           Configuration conf = new Configuration();
           FileSystem fs = FileSystem.get(URI.create(uri), conf);
           Path inputPath = new Path(uri);
           CompressionCodecFactory factory = new CompressionCodecFactory(conf);
           CompressionCodec codec = factory.getCodec(inputPath);
           if (codec == null) {
               System.err.println("No codec found for " + uri);
               System.exit(1);
           }
           String outputUri =
           CompressionCodecFactory.removeSuffix(uri, codec.getDefaultExtension());
           InputStream in null;
           OutputStream out = null;
           try {
               in = codec.createInputStream(fs.open(inputPath));
               out = fs.create(new Path(outputUri));
               IOUtils.copyBytes(in, out, conf);
           } finally {
               IOUtils.closeStream(in);
               IOUtils.closeStream(out);
           }
        }
    }

    编码/解码器一旦找到,就会被用来去掉文件名称后缀生成输出文件名称(通过CompressionCodecFactory的静态方法removeSuffix()来实现)。这样,例如以下调用程序便把一个名为file.gz的文件解压缩为file文件:
    % hadoop FileDecompressor file.gz
    CompressionCodecFactory 从io.compression.codecs配置属性定义的列表中找到编码/解码器。默认情况下。这个列表列出了Hadoop提供的全部编码/解码器 (见表4-3),假设你有一个希望要注冊的编码/解码器(如外部托管的LZO编码/解码器)你能够改变这个列表。每一个编码/解码器知道它的默认文件扩展 名,从而使CompressionCodecFactory能够通过搜索这个列表来找到一个给定的扩展名相匹配的编码/解码器(假设有的话)。
     属性名  类型  默认值  描写叙述
    io.compression.codecs 逗号分隔的类名 org.apache.hadoop.io.compress.DefaultCodec,
    org.apache.hadoop.io.compress.GzipCodec,
    org.apache.hadoop.io.compress.Bzip2Codec
    用于压缩/解压的CompressionCodec列表

     


    本地库

    考虑到性能,最好使用一个本地库(native library)来压缩和解压。

    比如,在一个測试中,使用本地gzip压缩库降低了解压时间50%,压缩时间大约降低了10%(与内置的Java实现相比 较)。表4-4展示了Java和本地提供的每一个压缩格式的实现。井不是全部的格式都有本地实现(比如bzip2压缩)。而还有一些则仅有本地实现(比如 LZO)。

     

    压缩格式 Java实现 本地实现
    DEFLATE
    gzip
    bzip2
    LZO

     

    Hadoop带有预置的32位和64位Linux的本地压缩库,位于库/本地文件夹。对于其它平台,须要自己编译库,详细请參见Hadoop的维基百科http://wiki.apache.org/hadoop/NativeHadoop。

    本地库通过Java系统属性java.library.path来使用。Hadoop的脚本在bin文件夹中已经设置好这个属性。但假设不使用该脚本,则须要在应用中设置属性。

    默认情况下,Hadoop会在它执行的平台上查找本地库,假设发现就自己主动载入。这意味着不必更改不论什么配置设置就能够使用本地库。在某些情况下,可能 希望禁用本地库,比方在调试压缩相关问题的时候。为此。将属性hadoop.native.lib设置为false,就可以确保内置的Java等同内置实现 被使用(假设它们可用的话)。

    CodecPool(压缩解码池)

    假设要用本地库在应用中大量运行压缩解压任务。能够考虑使用CodecPool,从而重用压缩程序和解压缩程序,节约创建这些对象的开销。

    下例所用的API仅仅创建了一个非常easy的压缩程序。因此不必使用这个池。此应用程序使用一个压缩池程序来压缩从标准输入读入然后将其写入标准愉出的数据:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class PooledStreamCompressor 
     {
        public static void main(String[] args) throws Exception 
        {
            String codecClassname = args[0];
            Class<?

    > codecClass = Class.forName(codecClassname);

            Configuration conf = new Configuration();
    CompressionCodec codec = (CompressionCodec) ReflectionUtils.newInstance(codecClass, conf);
            Compressor compressor = null;
            try {
    compressor = CodecPool.getCompressor(codec);//从缓冲池中为指定的CompressionCodec检索到一个Compressor实例
    CompressionOutputStream out = codec.createOutputStream(System.out, compressor);
                IOUtils.copyBytes(System.in, out, 4096, false);
                out.finish();
            } finally
            {
                CodecPool.returnCompressor(compressor);
            }
          }

    我 们从缓冲池中为指定的CompressionCodec检索到一个Compressor实例,codec的重载方法 createOutputStream()中使用的便是它。通过使用finally块,我们便可确保此压缩程序会被返回缓冲池,即使在复制数据流之间的字 节期间抛出了一个IOException。
    压缩和输入切割

    在考虑怎样压缩那些将由MapReduce处理的数据时,考虑压缩格式是否支持切割是非常重要的。考虑存储在HDFS中的未压缩的文件,其大小为1GB. HDFS块的大小为64MB ,所以文件将被存储为16块,将此文件用作输入的MapReduce会创建16个输入分片(split。也称为"分块"),每一个分片都被作为一个独立map任务的输入单独进行处理。

    如今如果,该文件是一个gzip格式的压缩文件,压缩后的大小为1GB。和前面一样。HDFS将此文件存储为 16块。然而,针对每一块创建一个分块是没实用的由于不可能从gzip数据流中的随意点開始读取,map任务也不可能独立于其分块仅仅读取一个分块中的数据。gZlp格式使用DEFLATE来存储压缩过的数据。DEFLATE 将数据作为一系列压缩过的块进行存储。问题是,每块的開始没有指定用户在数据流中随意点定位到下一个块的起始位置,而是其自身与数据流同步。

    因此,gzip不支持切割(块)机制。在这样的情况下,MapReduce不切割gzip格式的文件,由于它知道输入的是gzip格式(通过文件扩展名得知),而gzip压缩机制不支持切割机制。这样是以牺牲本地化为代价:一个map任务将处理16个HDFS块,大都不是map的本地数据。与此同一时候,由于map任务少,所以作业切割的粒度不够细,从而导致执行时间变长。


    在我们如果的样例中,如果是一个LZO格式的文件,我们会碰到相同的问题,因为基本压缩格式不为reader 提供方法使其与流同步。

    可是,bzip2格式的压缩文件确实提供了块与块之间的同步标记(一个48位的π近似值) 因此它支持切割机制。

    对于文件的收集。这些问题会稍有不同。ZIP是存档格式,因此t能够将多个文件合并为一个ZIP文件。每一个文单独压缩。全部文档的存储位置存储在ZIP文件的尾部。这个属性表明Z l P 文件支持文件边界处切割,每一个分片中包含ZIP压缩文件中的一个或多个文件。


    ZIP格式文件的结构例如以下图:ZIP文件结构请查看wiki



    在MapReduce 中使用压缩

    如前所述,假设输入的文件是压缩过的.那么在被MapReduce 读取时,它们会被自己主动解压。依据文件扩展名来决定应该使用哪一个压缩解码器。假设要压缩MapReduce作业的输出.请在作业配置文件里将mapred.output.compress属性设置为true,将mapred.output.compression.codec属性设置为自己打算使用的压缩编码/解码器的类名。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
        public class MaxTemperatureWithCompression {
     
            public static void main(String[] args) throws Exception {
                if (args.length != 2) {
                    System.err.println("Usage: MaxTemperatureWithCompression <input path> "
                            "<output path>");
                    System.exit(-1);
                }
                Job job = new Job();
                job.setJarByClass(MaxTemperature.class);
                FileInputFormat.addInputPath(job, new Path(args[0]));
                FileOutputFormat.setOutputPath(job, new Path(args[1]));
                job.setOutputKeyClass(Text.class);
                job.setOutputValueClass(IntWritable.class);
                FileOutputFormat.setCompressOutput(job, true);
                FileOutputFormat.setOutputCompressorClass(job, GzipCodec.class);
                job.setMapperClass(MaxTemperatureMapper.class);
                job.setCombinerClass(MaxTemperatureReducer.class);
                job.setReducerClass(MaxTemperatureReducer.class);
                System.exit(job.waitForCompletion(true) ? 0 1);
            }
        }

    我们使用压缩过的输入来执行此应用程序(事实上不必像它一样使用和输入同样的格式压缩输出),例如以下所看到的:
    % hadoop MaxTemperatur eWithCompression input/ ncdc/sample.txt.gz
    output
    量生终输出的每部分都是压缩过的.在本例中。仅仅有一部分:
    % gunzip -c output/part-OOOOO.gz
    1949 111
    1950 22
    假设为输出使用了一系列文件, 能够设置mapred.output.compresson.type 属性来控制压缩类型。默觉得RECORD,它压缩单独的记录。将它改为BLOCK,可以压缩一组记录,因为有更好的压缩比。所以推荐使用。

    map 作业输出结果的压缩
    即使MapReduce 应用使用非压缩的数据来读取和写入,我们也能够受益于压缩map阶段的中阔输出。由于map作业的输出会被写入磁盘并通过网络传输到reducer节点,所以假设使用LZO之类的高速压缩,能得到更好的性能,由于传输的数据量大大降低了.表4-5显示了启用map输出压缩和设置压缩格式的配置属性.

    以下几行代码用于在map 作业中启用gzi p 格式来压缩输出结果:
    1
    2
    3
    4
    5
    Configuration conf = new Configuration();
    conf.setBoolean("mapred.compress.map.output"true);
    conf.setClass("mapred.map.output.compression.codec", GzipCodec.class,
    CompressionCodec.class);
    Job job = new Job(conf);
    旧的API要这样配置
    1
    2
    conf.setCompressMapOutput(true);
    conf.setMapOutputCompressorClass(GzipCodec.class);

    压缩就到此为止了。总之编码和解码在hadoop有着关键的作用。




    Charles 于P.P 



    版权说明:
    本文由Charles Dong原创,本人支持开源以及免费故意的传播。反对商业化谋利。
    CSDN博客:http://blog.csdn.net/mrcharles
    个人站:http://blog.xingbod.cn
    EMAIL:charles@xingbod.cn
  • 相关阅读:
    JavaScript学习记录总结(九)——移动添加效果
    JavaScript学习记录总结(十)——几个重要的BOM对象
    Hibernate——property的access属性
    JavaWeb学习记录(一)——response响应头之缓存设置与下载功能的实现
    使用配置方式进行ssh的整合以及管理员管理的案例(二)
    Hibernate设置派生属性(formula)
    JavaScript学习记录总结(五)——servlet将json数据写出去
    反射
    集合详解
    包装器类型
  • 原文地址:https://www.cnblogs.com/wzzkaifa/p/7233553.html
Copyright © 2020-2023  润新知