《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,Compressor和Decompressor仅仅是压缩器和解压器,用于提供压缩和解压的功能,所以并不存有数据。我们从CodecPool中提取压缩器和解压器只是为了构建压缩流。
codec.createOutputStream(out,compressor)
使用压缩器compressor,在底层输出流out的基础上创建对应的压缩流。
如果不用codecpool每创建一个CompressionOutputStream就会调用createOutputStream(OutputStream 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。