《hadoop权威指南》学习笔记-hadoop I/O之压缩

本文深入探讨了Hadoop中的压缩机制,包括压缩类型及其优势、压缩与解压缩的实现原理、codecpool的优化作用以及如何在MapReduce作业中应用压缩。

《Hadoop权威指南》第四章是介绍的Hadoop I/O,内容很多,而且有些地方讲的不是太详细,有些代码只有自己写一个不同的才能理解,所以分为四篇博客来写。这一篇主要讲压缩。

书上直接先讲压缩类型,这几种压缩类型各有各的优势,bzip2可以于分片,其他的都不行,gzip比较折中,压缩速度和压缩能力相对较为平衡,snappy被谷歌推崇,似乎是安全性更好而且压缩速度和解压速度相当快,尤其在64位X86上效率会更高。其他的类型没有深究。

一、这本书对Compressioncodec的介绍有些让人迷惑,说的是不错,Compressioncodec实现了一种压缩与解压缩的算法,但是不够透彻,以至于当你读到codecpool的内容的时候会对compressor产生疑惑。所以在查看大量博客后,有一篇是介绍到压缩的源码和内部机制,这才明白,其实compressor才是真正的提供了压缩功能,Compressioncodec只是一个接口,所以才会指向ReflectionUtils.newInstance创建的一个实例,而这个实例是继承了这个接口的,而且已经实现了这个接口里的函数,其中就有createOutputStream,这个方法有重载,一个是只根据底层提供的OutputStream来创建压缩流(也就是说压缩流会写入底层的OutputStream),自己实现压缩功能(马上就可以看到代码),而另一个是根据压缩器(即compressor)提供的功能来创建压缩流,这个压缩流会写入底层的OutputStream(在codecpool的代码中会看到)。

先对书上提供的代码进行分析,由于书上的代码对原理的阐述给人的感觉不太直观,然后再附上我的代码。

代码1:

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.compress.CompressionCodec;
import org.apache.hadoop.io.compress.CompressionOutputStream;
import org.apache.hadoop.util.ReflectionUtils;

// vv StreamCompressor
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();
  }
}


summerdg@summerdg-virtual-machine:~/hadoop-1.2.1/URLCat$ echo "hello world"|../bin/hadoop StreamCompressor

org.apache.hadoop.io.compress.GzipCodec |gunzip

13/11/01 19:43:17 INFO util.NativeCodeLoader: Loaded the native-hadoop library

13/11/01 19:43:17 INFO zlib.ZlibFactory: Successfully loaded & initialized native-zlib library

hello world

书上利用管道命令作为输入输出,所以不是太明显。利用echo输入内容(输入到System.in)里边。改程序在使用的时候还要输入类的全称,然后再加载类。利用ReflectionUtils.newInstance来创建一个继承了Compressioncodec接口的实例对象,然后调用createOutputStream用底层输出流创建一个压缩流,这个压缩流是System.out所以不会存储在文本里,会直接输出到屏幕,以至于我们必须用管道命令解压缩输出的内容才可以看到。

下面是我的代码,直接把一个文件压缩,输出到指定的路径,顺便也复习一下上一章的内容,毕竟亲一篇博客贴了大量的原书代码,比较水。

代码2:

import java.net.URI;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.compress.CompressionCodec;
import org.apache.hadoop.io.compress.CompressionOutputStream;
import org.apache.hadoop.util.ReflectionUtils;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;

// vv StreamCompressor
public class FileCompress {

  public static void main(String[] args) throws Exception {
    String tail = args[0];
    String type;
    switch(tail){
    case "defualt":
        type = new String("DefaultCodec");
        break;
    case "gzip":
        type = new String("GzipCodec");
        break;
    case "bzip2":
        type = new String("Bzip2Codec");
        break;
    case "LZ4":
        type = new String("Lz4Codec");
        break;
    case "snappy":
        type = new String("SnappyCodec");
        break;
    default:
        return;
    }
    String codecClassname ="org.apache.hadoop.io.compress" + "." + type;
    String input = args[1];
    Class<?> codecClass = Class.forName(codecClassname);
    Configuration conf = new Configuration();
    CompressionCodec codec = (CompressionCodec)ReflectionUtils.newInstance(codecClass, conf);

    FileSystem fs=FileSystem.get(URI.create(input),conf);
    FSDataInputStream in = null;

        CompressionOutputStream outStream=null;
    try{
        in = fs.open(new Path(input));
        Path op2=new Path(input+"."+codec.getDefaultExtension());
        outStream=codec.createOutputStream(fs.create(op2));       
        IOUtils.copyBytes(in, outStream, 4096,false);  
    }finally{
        IOUtils.closeStream(in);
	outStream.finish();//.finish() is very important, couse it impress the end of file
    }
  }
}


summerdg@summerdg-virtual-machine:~/hadoop-1.2.1/URLCat$ ../bin/hadoop FileCompress gzip /test/file01

13/11/01 19:35:15 INFO util.NativeCodeLoader: Loaded the native-hadoop library

13/11/01 19:35:15 INFO zlib.ZlibFactory: Successfully loaded & initialized native-zlib library

summerdg@summerdg-virtual-machine:~/hadoop-1.2.1/URLCat$ ../bin/hadoop fs -ls /test

Found 2 items

-rw-r--r--   1 summerdg supergroup         22 2013-11-01 19:34 /test/file01

-rw-r--r--   1 summerdg supergroup         37 2013-11-01 19:35 /test/file01.gz


分析过程还是一样的,只是输入和输出的方式有所变化。

 

二、下面介绍解压缩,解压缩的内容,书上介绍的很好很透彻,这回的区别就是调用了CompressionCodecFactory这个类,这个类可以通过调用getCodec()来得到CompressionCodec的一个实例,同样这个是例已经实现了createInputStream,通过已给底层输入流就可以创建解压缩流了。整个分析过程和压缩是一样的,包括这个方法的重载。

代码3:

import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;

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;

// vv FileDecompressor
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);
    }
  }
}


summerdg@summerdg-virtual-machine:~/hadoop-1.2.1/URLCat$ ../bin/hadoop fs -rm /test/file01

Deleted hdfs://localhost:9000/test/file01

summerdg@summerdg-virtual-machine:~/hadoop-1.2.1/URLCat$ ../bin/hadoop fs -ls /test/

Found 1 items

-rw-r--r--   1 summerdg supergroup         37 2013-11-01 19:51 /test/file01.gz

summerdg@summerdg-virtual-machine:~/hadoop-1.2.1/URLCat$ ../bin/hadoop FileDecompressor /test/file01.gz

13/11/01 19:52:22 INFO util.NativeCodeLoader: Loaded the native-hadoop library

13/11/01 19:52:22 WARN snappy.LoadSnappy: Snappy native library not loaded

13/11/01 19:52:22 INFO zlib.ZlibFactory: Successfully loaded & initialized native-zlib library

summerdg@summerdg-virtual-machine:~/hadoop-1.2.1/URLCat$ ../bin/hadoop fs -ls /test/

Found 2 items

-rw-r--r--   1 summerdg supergroup         22 2013-11-01 19:52 /test/file01

-rw-r--r--   1 summerdg supergroup         37 2013-11-01 19:51 /test/file01.gz

三、codecpool这部分就是对频繁地压缩和解压缩进行的优化设计,现附上原书代码(此处即使自己写代码也不能很好体现它的优化效果)

代码4:

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

// vv PooledStreamCompressor
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);/*]*/
      CompressionOutputStream out =
        codec.createOutputStream(System.out, /*[*/compressor/*]*/);
      IOUtils.copyBytes(System.in, out, 4096, false);
      out.finish();
    /*[*/} finally {
      CodecPool.returnCompressor(compressor);
    }/*]*/
  }
}


看完这部分代码,其实只是实现了代码1的功能,不同的地方就是利用了compressor,CompressorDecompressor仅仅是压缩器和解压器,用于提供压缩和解压的功能,所以并不存有数据。我们从CodecPool中提取压缩器和解压器只是为了构建压缩流。

codec.createOutputStream(out,compressor)

使用压缩器compressor,在底层输出流out的基础上创建对应的压缩流。

 

如果不用codecpool每创建一个CompressionOutputStream就会调用createOutputStreamOutputStream out)来实现压缩的方法频繁做消耗的资源是很大的,codecpool就是让你反复去利用相同类型的压缩器,以达到分摊开销的目的,通俗一点就是让你不用每次都去实现相应的压缩功能。

四、压缩与输入分片这部分没什么好讲的,实现过程只是把输入变为压缩文件就行了,区别就在于不同的压缩类型执行的效率不同,有的类型支持分片,那么map的时候效率就高,不支持分片的效率自然就低。而支持分片的就只有bzip2了。

五、MapReduce中使用压缩,意思就是把mapreduce的逐出结果变为压缩文件,只要在configuration变量里做相应的设置就可以了。

像这样(把输出格式设为gzip):

FileOutputFormat.setCompressOutput(job, true);

FileOutputFormat.setOutputCompressorClass(job, GzipCodec.class);

六、最后一部分介绍了压缩Map任务的输出,文中没有仔细介绍,但我觉得这个压缩还是蛮重要的,因为压缩过后的文件更节省带宽,这样更有利于Reducer的处理。做法其实也很简单,和上面的设置位置一样,都在MapReduce的main函数里,然后

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);

上面的代码是将map的输出设为gzip。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值