数据压缩简介
-
压缩技术能够有效减少底层存储系统(HDFS)读写字节数。压缩提高了网络带宽和磁盘空间的效率。在Hadoop下,尤其是数据规模很大和工作负载密集的情况下,使用数据压缩显得非常重要。在这种情况下,I/O操作和网络数据传输要花大量的时间。还有,Shuffle与Merge过程同样也面临着巨大的I/O压力。
-
鉴于磁盘I/O和网络带宽是Hadoop的宝贵资源,数据压缩对于节省资源、最小化磁盘I/O和网络传输非常有帮助。不过,尽管压缩与解压操作的CPU开销不高,其性能的提升和资源的节省并非没有代价。
-
如果磁盘I/O和网络带宽影响了MapReduce作业性能,在任意MapReduce阶段启用压缩都可以改善端到端处理时间并减少I/O和网络流量。
-
压缩Mapreduce的一种优化策略:通过压缩编码对Mapper或者Reducer的输出进行压缩,以减少磁盘IO,提高MR程序运行速度(但相应增加了cpu运算负担)。
使用原则
(1)运算密集型的job,少用压缩
(2)IO密集型的job,多用压缩
常用压缩算法
参考https://blog.csdn.net/linuxnc/article/details/44499231
hadoop中的压缩算法
bzip2 org.apache.hadoop.io.compress.BZip2Codec
LZO com.hadoop.compression.lzo.LzopCodec
DEFLATE org.apache.hadoop.io.compress.DefaultCodec
gzip org.apache.hadoop.io.compress.GzipCodec
Snappy org.apache.hadoop.io.compress.SnappyCodec
性能比较
压缩算法 原始文件大小 压缩文件大小 压缩速度 解压速度
gzip 8.3GB 1.8GB 17.5MB/s 58MB/s
bzip2 8.3GB 1.1GB 2.4MB/s 9.5MB/s
LZO 8.3GB 2.9GB 49.3MB/s 74.6MB/s
参考http://google.github.io/snappy/
详细介绍
Gzip压缩
优点:压缩率比较高,速度快;hadoop支持;大部分linux系统都自带gzip命令,方便。
缺点:不支持split。
Bzip2压缩
优点:压缩率高;支持spli;hadoop本身支持,但不支持native;在linux系统下自带bzip2命令,使用方便。
缺点:压/解速度慢;不支持native。
Lzo压缩
优点:压缩/解压速度也比较快,合理的压缩率;支持split,是hadoop中最流行的压缩格式;可以在linux系统下安装lzop命令,使用方便。
缺点:压缩率比gzip要低一些;hadoop本身不支持,需要安装;在应用中对lzo格式的文件需要做一些特殊处理(为了支持split需要建索引,还需要指定inputformat为lzo格式)。
Snappy压缩
优点:高速压缩速度和合理的压缩率。
缺点:不支持split;压缩率比gzip要低;hadoop本身不支持,需要安装;
优缺点参考潭州教育。
实现
现在我们动手实现一个具有压缩和二次排序的wordcount MR程序,前面的blog已经完成了其他部分,我们这一次主要是增加数据压缩和二次排序。
其实上一章使用partitioner和WritableComparable已经实现了不同partition文件的排序,但是这在真正的生产应用中是不可行的,这一次我们使用groupingComparator来实现不同partition文件内的数据排序,也叫做二次排序。
首先增加groupingcomparator类
package MRcode;
import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.io.WritableComparator;
/**
* @Author: Braylon
* @Date: 2020/1/21 17:20
* @Version: 1.0
*/
public class groupingComparator extends WritableComparator {
public groupingComparator() {
super(dataBean.class, true);
}
@Override
public int compare(WritableComparable a, WritableComparable b) {
dataBean d0 = new dataBean();
dataBean d1 = new dataBean();
d0 = (dataBean) a;
d1 = (dataBean) b;
int mark = 0;
if (d0.getData0() < d1.getData0()) {
mark = 1;
} else {
mark = 0;
}
return mark;
}
}
这里承接我原来blog中的wordcount例子,所以具体其他类的代码 和目录结构请参考原来的blog。
重点是继承了WritableComparator,重写compare方法,具体逻辑只需要读代码就可以明白,这里不做过多讲解,总之这是实现了当分完partition之后的文件内数据排序。这里为了方便我只写了dataBean的一个属性的,其实可以很容易的做到多属性的排序。
修改driver类
package MRcode;
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.BZip2Codec;
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 WorkCountDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
args = new String[] {"inputpath","outputpath"};
//获取配置信息
Configuration conf = new Configuration();
//开启map端压缩
// conf.setBoolean("mapreduce.map.output.compress", true);
// conf.setClass("mapreduce.map.output.compress.codec", GzipCodec.class, CompressionCodec.class);
Job job = Job.getInstance(conf);
job.setJarByClass(WorkCountDriver.class);
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
//map输出类型 也就是reducer的输入
job.setMapOutputKeyClass(dataBean.class);
job.setMapOutputValueClass(IntWritable.class);
//reduce 输出的K,V类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
job.setGroupingComparatorClass(groupingComparator.class);
job.setPartitionerClass(partitioner.class);
job.setNumReduceTasks(2);
FileInputFormat.setInputPaths(job,new Path(args[0]));
FileOutputFormat.setOutputPath(job,new Path(args[1]));
//设置reduce端的数据压缩
FileOutputFormat.setCompressOutput(job, true);
FileOutputFormat.setOutputCompressorClass(job, BZip2Codec.class);
//提交job
job.waitForCompletion(true);
}
}
这里我直接加上了map和reduce阶段的两个数据压缩,就是这么简单。
下面我会介绍一个简单的数据压缩demo,独立于mapreduce框架以外的。
数据压缩实现
目录结构
compress
package dataCompress;
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.testng.annotations.Test;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
/**
* @Author: Braylon
* @Date: 2020/1/21 16:31
* @Version: 1.0
*/
public class compress {
public void compressed(String fileName, String method) throws Exception {
FileInputStream fis = new FileInputStream(fileName);
//反射找到编码解码器类
Class codeClass = Class.forName(method);
//通过反射工具类找到编码器对象和配置
CompressionCodec codec = (CompressionCodec) ReflectionUtils.newInstance(codeClass, new Configuration());
//创建文件输出流
FileOutputStream fos = new FileOutputStream(new File(fileName + codec.getDefaultExtension()));
//获得编码器的输出流
CompressionOutputStream cos = codec.createOutputStream(fos);
//流拷贝
IOUtils.copyBytes(fis, cos, 5 * 1024 * 1024, false);
//关闭流
cos.close();
fos.close();
fis.close();
}
@Test
public void conpressTest() throws Exception {
compressed("hello.txt", "org.apache.hadoop.io.compress.GzipCodec");
}
}
depress
package dataCompress;
import com.sun.tools.internal.xjc.outline.FieldOutline;
import org.apache.hadoop.conf.Configuration;
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.CompressionInputStream;
import org.testng.annotations.Test;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
/**
* @Author: Braylon
* @Date: 2020/1/21 16:46
* @Version: 1.0
*/
public class depress {
public void depressed(String fileName, String decoded) throws Exception {
//获取实例
CompressionCodecFactory factory = new CompressionCodecFactory(new Configuration());
CompressionCodec codec = factory.getCodec(new Path(fileName));
//输入流
CompressionInputStream cis = codec.createInputStream(new FileInputStream(new File(fileName)));
//输出流
FileOutputStream fos = new FileOutputStream(new File(fileName + decoded));
IOUtils.copyBytes(cis, fos, 5 * 1024 * 1024, false);
//关闭流
fos.close();
cis.close();
}
@Test
public void depressTest() throws Exception {
depressed("hello.txt.gz", ".txt");
}
}
这一章比较简单,如果有错误的话还请大家指出。
共勉~