2.1.4 hadoop HDFS的数据压缩

文件压缩两大好处:

     1)、减少存储文件所需要的存储空间;

     2)、加速数据在磁盘和网络上的传输。

1、我们可以把数据文件压缩后再存入HDFS,以节省存储空间。但是,在使用MapReduce处理压缩文件时,必须考虑压缩文件的可分割性。

(转自:http://www.cnblogs.com/zhengrunjian/p/4527165.html)

      hadoop里支持很多种压缩格式,我们看一个表格:

      DEFLATE是同时使用了LZ77算法与哈夫曼编码(Huffman Coding)的一个无损数据压缩算法,源代码可以在zlib库中找到。gzip是以DEFLATE算法为基础扩展出来的一种算法。所有的压缩算法都是空间和时间的转换,更快压缩时间还是更小的压缩比,可以通过参数来指定,-1意味着速度,-9意味着空间。拿gzip做个例子,下面就意味着更快速的压缩:

      gzip -1 file  

      gzip在时间和空间上的取舍比较折中,bzip2压缩比gzip更有效,但是速度更慢。bzip2的解压速度比它的压缩速度要快。但是和其他压缩格式比又是最慢的,但是压缩效果明显是最好的。snappy和lz4的解压速度比lzo好很多。

     splittable表示压缩格式是否可以被分割,也就是说是否支持随即读。压缩数据是否能被mapreduce使用,压缩数据是否能被分割就很关键了。

      举个例子,一个未压缩的文件有1GB大小,hdfs默认的block大小是64MB,那么这个文件就会被分为16个block作为mapreduce的输入,每一个单独使用一个map任务。如果这个文件是已经使用gzip压缩的呢,如果分成16个块,每个块做成一个输入,显然是不合适的,因为gzip压缩流的随即读是不可能的。实际上,当mapreduce处理压缩格式的文件的时候它会认识到这是一个gzip的压缩文件,而gzip又不支持随即读,它就会把16个块分给一个map去处理,这里就会有很多非本地处理的map任务,整个过程耗费的时间就会相当长。

      lzo压缩格式也会是同样的问题,但是通过使用hadoop lzo库的索引工具以后,lzo就可以支持splittable。bzip2也是支持splittable的。那么如何选择压缩格式呢?这取决于文件的大小,你使用的压缩工具,下面是几条选择建议,效率由高到低排序

      1)、用一些包含了压缩并且支持splittable的文件格式,比如Sequence File,RCFile或者Avro文件,这些文件格式我们之后都会讲到。如果为了快速压缩可以使用lzo,lz4或者snappy压缩格式

      2)、使用提供splittable的压缩格式,比如,bzip2和索引后可以支持splittable的lzo。

      3)、提前把文件分成几个块,每个块单独压缩,这样就无需考虑splittable的问题了

      4)、不要压缩文件

 提示:以不支持splittable的压缩格式存储一个很大的数据文件是不合适的,非本地处理效率会非常之低。

    【四种常用压缩工具介绍】

1)、gzip压缩

优点:压缩率比较高,而且压缩/解压速度也比较快;hadoop本身支持,在应用中处理gzip格式的文件就和直接处理文本一样;有hadoop native库;大部分linux系统都自带gzip命令,使用方便。

缺点:不支持split。

应用场景:当每个文件压缩之后在130M以内的(1个块大小内),都可以考虑用gzip压缩格式。譬如说一天或者一个小时的日志压缩成一个gzip文件,运行mapreduce程序的时候通过多个gzip文件达到并发。hive程序,streaming程序,和java写的mapreduce程序完全和文本处理一样,压缩之后原来的程序不需要做任何修改。

2)、lzo压缩

优点:压缩/解压速度也比较快,合理的压缩率;支持split,是hadoop中最流行的压缩格式;支持hadoop native库;可以在linux系统下安装lzop命令,使用方便。

缺点:压缩率比gzip要低一些;hadoop本身不支持,需要安装;在应用中对lzo格式的文件需要做一些特殊处理(为了支持split需要建索引,还需要指定inputformat为lzo格式)。

应用场景:一个很大的文本文件,压缩之后还大于200M以上的可以考虑,而且单个文件越大,lzo优点越明显。

3)、snappy压缩

优点:高速压缩速度和合理的压缩率;支持hadoop native库。

缺点:不支持split;压缩率比gzip要低;hadoop本身不支持,需要安装;linux系统下没有对应的命令。

应用场景:当mapreduce作业的map输出的数据比较大的时候,作为map到reduce的中间数据的压缩格式;或者作为一个mapreduce作业的输出和另外一个mapreduce作业的输入。

4 bzip2压缩

优点:支持split;具有很高的压缩率,比gzip压缩率都高;hadoop本身支持,但不支持native;在linux系统下自带bzip2命令,使用方便。

缺点:压缩/解压速度慢;不支持native。

应用场景:适合对速度要求不高,但需要较高的压缩率的时候,可以作为mapreduce作业的输出格式;或者输出之后的数据比较大,处理之后的数据需要压缩存档减少磁盘空间并且以后数据用得比较少的情况;或者对单个很大的文本文件想压缩减少存储空间,同时又需要支持split,而且兼容之前的应用程序(即应用程序不需要修改)的情况。

   4种压缩格式的特征的比较:

 

 

压缩格式splitnative压缩率速度是否hadoop自带linux命令换成压缩格式后,原来的应用程序是否要修改
gzip 很高 比较快 是,直接使用 和文本处理一样,不需要修改
lzo 比较高 很快 否,需要安装 需要建索引,还需要指定输入格式
snappy 比较高 很快 否,需要安装 没有 和文本处理一样,不需要修改
bzip2 最高 是,直接使用 和文本处理一样,不需要修改

 

二、实现代码 

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.Date;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.compress.CompressionCodec;
import org.apache.hadoop.io.compress.CompressionCodecFactory;
import org.apache.hadoop.io.compress.CompressionOutputStream;
import org.apache.hadoop.util.ReflectionUtils;
public class FileCompression {
//压缩时main函数实参  compress BZip2Codec  hdfs://Master:9000/user/hadoop/input/3.txt hdfs://Master:9000/user/hadoop/input/3.bz2
//解压缩传递参数decompress BZip2Codec  hdfs://Master:9000/user/hadoop/input/3.bz2 hdfs://Master:9000/user/hadoop/input/4.txt     
    public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException {  
        if (args[0].equals("compress")) {  
            compress("org.apache.hadoop.io.compress." + args[1],args[2],args[3]);  
        }         
        else if (args[0].equals("decompress"))  
            decompres(args[2],args[3]);  
        else {  
            System.err.println("Error!\n usgae: hadoop jar Hello.jar [compress] [filename] [compress type]");  
            System.err.println("\t\ror [decompress] [filename] ");  
            return;  
        }  
        System.out.println("down");  
    }  
    /* 
     * filename是希望压缩的原始文件,method是欲使用的压缩方法(如BZip2Codec等) 
     */  
    public static void compress(String method,String scrPath,String targetPath) throws ClassNotFoundException, IOException
    {  
        System.out.println("[" + new Date() + "] : enter compress");        
        Configuration conf = new Configuration(); 
        FileSystem fileSystem=FileSystem.get(URI.create(scrPath), conf);
        //获取hdfs文件
        InputStream in=fileSystem.open(new Path(scrPath));      
        //动态加载和创建CLASS对象,创建压缩方法类
        Class<?> codecClass = Class.forName(method);         
                 
        // 通过名称找到对应的编码/解码器  
        //CompressionCodec 该类负责对数据流进行压缩和解压缩
        //ReflectionUtils用于实例化编码/解码器对象
        CompressionCodec codec = (CompressionCodec)ReflectionUtils.newInstance(codecClass, conf);   
        //hdfs保存
        OutputStream out=fileSystem.create(new Path(targetPath));
        //对输出流进行压缩
        CompressionOutputStream cout = codec.createOutputStream(out);           
        System.out.println("[" + new Date() + "]: start compressing ");  
        IOUtils.copyBytes(in, cout, 1024*1024*5, false);        // 缓冲区设为5MB  
        System.out.println("[" + new Date() + "]: compressing finished ");           
        in.close();  
        //完成到压缩数据流的写操作,但不关闭该数据流
        cout.finish();
    }       
    /* 
     * filename是希望解压的文件 
     */  
    public static void decompres(String scrPath,String targetPath) throws FileNotFoundException, IOException 
    {  
        System.out.println("[" + new Date() + "] : enter compress");           
        Configuration conf = new Configuration();
        FileSystem fileSystem=FileSystem.get(URI.create(scrPath), conf);      
        CompressionCodecFactory factory = new CompressionCodecFactory(conf);
        //根据文件的扩展名获取CompressionCodec的方法
        CompressionCodec codec = factory.getCodec(new Path(scrPath)); 
        
        if (null == codec) 
        {  
            System.out.println("Cannot find codec for file " + scrPath);  
            return;  
        }          
        InputStream cin = codec.createInputStream(fileSystem.open(new Path(scrPath)));  
        OutputStream out =fileSystem.create(new Path(targetPath));         
        System.out.println("[" + new Date() + "]: start decompressing ");  
        IOUtils.copyBytes(cin, out, 1024*1024*5, false);  
        System.out.println("[" + new Date() + "]: decompressing finished ");          
        IOUtils.closeStream(cin);
        IOUtils.closeStream(out);
    }  
}
View Code

在mapreduce中对输出进行压缩:

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.compress.GzipCodec;
import org.apache.hadoop.mapred.TextInputFormat;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;

public class WordCount {
    public static void main(String[] args) throws Exception {
        Configuration conf = new Configuration();    
        String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();    
    
        if (otherArgs.length != 2) {
            System.err.println("Usage: wordcount <in> <out>"+otherArgs.length);    
            System.exit(2);        
        }    
        Job job = new Job(conf, "word count");    
        job.setJarByClass(WordCount.class);    
        job.setMapperClass(TokenizerMapper.class);
        job.setCombinerClass(IntSumReducer.class);
        job.setReducerClass(IntSumReducer.class);
        
        //将作业输出压缩,另外如果不写下面代码通过配置的方式也可达到该效果(mapred.output.compress属性设置为ture;mapred.output.compression.codec属性设置为打算使用的压缩codc的类名)
        FileOutputFormat.setCompressOutput(job, true);
        FileOutputFormat.setOutputCompressorClass(job, GzipCodec.class);
        
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);
        FileInputFormat.addInputPath(job, new Path(otherArgs[0]));
        FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]));    
        System.exit(job.waitForCompletion(true) ? 0 : 1);
    }    
}
View Code

三、在hadoop的mapreduce中使用gzip

(转自:http://blog.csdn.net/needle2/article/details/6824972)

  1. 从压缩的输入文 件时直接读入
    由于hadoop在读取输入文件时,会很智能的根据输入文件的后缀名来进行判断是否采用压缩格式进行读入,所以当读到输入文件 是***.gz时,就会猜测该文件是一个用gzip压缩过的文件,于是就会尝试使用gzip的读取方式来读取.
  2. 将mapreduce job所产生的中间结果进行压缩
    由于mapreduce算法本身的特征,必然会在job的运行过程中产生一定的中间结果文件,当数据 量很大的时候,这些中间结果也非常的客观,一定程度上, 对job的效率会有一定的影响。由于通常计算任务的瓶颈都在磁盘的读写IO上,因此如果能够减少因中间文件而产生的disk IO,则对作业的效率肯定有益无害。因此如果希望将mapreduce作业的中间结果进行压缩,在hadoop的conf(可以通过修改hadoop- site.xml的配置选项,可以在程序中用JobConf的类接口设 置 ,或者在提交作业时用-D选项来设置该选项)中配置一个选项:
    <property>
        <name>mapred.compress.map.output</name>
        <value>true</value>
    </property>
    这样,作业就会将产生的中间结果写 入slave local的时候,对结果进行压缩,reduce读取时也能够根据gz的后缀知道该中间结果是压缩文件,于是采用相应的读取格式来读取。
  3. 将 最终的计算输出的结果进行压缩
    有的时候,我们需要对作业的运行结果进行历史保存,但是如果每天积累的计算结果非常大,又想要保存尽量多的历史结果 一边将来的结算,那么日积月累下,就会占据非常非常大的HDFS的存储空 间 ,并且由于是历史数据,使用的频率也不高,这就会造成很大的存储空间的浪费,因此,将计算的结果进行压缩,也是一种 非常好的节省空间的方法。要在hadoop job中做到这一点也很容易,只需要告诉hadoop,“我想要多job的输出结果进行压缩,并保存到HDFS上去”,就行了。具体的操作就是:在 conf中设置配置选项:
    <property>
           <name>mapred.output.compress</name>
           <value>true</value>
    </property>

       以下是使用hadoop-0.19.1对一个11358 Byte的文件进行三种方式的压缩的对比:

  • 读取非压缩文件,中间结果不 压缩,输出结果也不压缩
   HDFS bytes read  HDFS bytes written Local bytes read Local bytes written
 Map  13872  0  0 9922
 Reduce  0  6117 9860 9860
 Total  13872 6117 9860 19782

 

  • 读 取非压缩文件,中间结果压缩,输出结果不压缩

 

   HDFS bytes read HDFS bytes written Local bytes read Local bytes written
 Map  13872 0  0 9922
 Reduce  0  2663  9860  9860
 Total  13872  2663  9860  19782

 

  • 读 取非压缩文件,中间结果压缩,输出结果也压缩

 

  HDFS bytes raed HDFS bytes written Local bytes read Local bytes written
 Map  13872  0 0 4221
 Reduce  0  2663  3623  3423
 Total  13872  2663  3623  7844

     
        因此我们可以看到,在hadoop中使用gzip压缩来进行读取,中间结果,数据结果的保存都是非常的容易,因为hadoop native本身就提供了我们相应的类来解析和压缩数据。不过这里要特别提到的是:gzip压缩格式在hadoop中的支持有一定的局限性:  由于gzip压缩算法本身的原因,我们无法对gzip压缩文件进行分块,也就是说,在hadoop中,如果想要用hadoop 的mapreduce来处理数据,没一个mapper就必须对应一个gz文件,不能多个mapper对一个gzip文件的多个chunk进行并行的处理, 因此如果要在hadoop mapreduce任务中使用gzip,在数据处理之前就需要对数据进行认为的切分,让一个mapper来处理一块数据。这样其实有一点有违 mapreduce的本质。

 
 
posted @ 2017-05-12 17:18  royis  阅读(1509)  评论(0编辑  收藏  举报