Hadoop 从 0 到 1 学习 ——第十二章 Hadoop 数据压缩

1. 压缩概述

压缩计算能够有效减少底层存储系统读写字节数。压缩提高了网络带宽和磁盘空间的效率。在运行 MR 程序时, I/O 操作、网络传输、Shuffle 和 Merge 要花大量的时间,尤其是数据规模很大和工作负载密集的情况下,因此,使用数据压缩显得非常重要

鉴于磁盘 I/O 和网络带宽是 Hadoop 的宝贵资源,数据压缩对于节省资源、最小化磁盘 I/O 和网络传输非常有帮助可以在任意 MapReduce 阶段启用压缩。不过,尽管压缩和解压缩操作的 CPU 开销不高,其型男的提示和资源的节省并发没有代价。

2. 压缩策略和原则

压缩是提高 Hadoop 运行效率的一种优化策略。

通过对 Mapper、Reducer 运行过程的数据进行压缩,以减少磁盘 IO,提高 MR 程序运行速度。

注意:采用压缩技术减少了磁盘 IO,但同时增加了CPU 运算负担。所以、压缩特性运用得当能提升性能,运用不当也可能降低性能。

压缩基本原则:

  1. 运算密集型的 Job,少用压缩。
  2. IO 密集型的 Job、多用压缩。

3. MR 支持的压缩编码

压缩格式 hadoop自带? 算法 文件扩展名 是否可切分 换成压缩格式后,原来的程序是否需要修改
DEFLATE 是,直接使用 DEFLATE .deflate 和文本处理一样,不需要修改
Gzip 是,直接使用 DEFLATE .gz 和文本处理一样,不需要修改
bzip2 是,直接使用 bzip2 .bz2 和文本处理一样,不需要修改
LZO 否,需要安装 LZO .lzo 需要建索引,还需要指定输入格式
Snappy 否,需要安装 Snappy .snappy 和文本处理一样,不需要修改

为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器,如下表所示:

压缩格式 对应的编码/解码器
DEFLATE org.apache.hadoop.io.compress.DefaultCodec
gzip org.apache.hadoop.io.compress.GzipCodec
bzip2 org.apache.hadoop.io.compress.BZip2Codec
LZO com.hadoop.compression.lzo.LzopCodec
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/

在64 为 单核 i7 CUP 上,Snappy 的压缩效率是:250MB/s 甚至更高,解压缩效率是 500MB/s甚至更高。

4. 压缩方式选择

4.1 Gzip 压缩

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

缺点:不支持 Split。

应用场景:当每个文件压缩之后再 130M 以内的 (一个块大小内),都可以考虑使用 Gzip 格式压缩。例如说一天或者一个小时的日志压缩成一个 Gzip 文件。

4.2 Bzip2 压缩

优点:支持 Split,具有很高的压缩率,比 Gzip 压缩率都高。Hadoop本身自带,使用方便。

缺点:压缩/解压缩速度慢。

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

4.3 Lzo 压缩

优点:压缩/解压缩速度比较快,合理的压缩率。支持 Split,是 Hadoop 中最流行的压缩格式。可以在 Linux 系统下安装 老婆命令,使用方便。

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

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

4.4 Snappy 压缩

优点:高速压缩速度和合理的压缩率

缺点:不支持 Split。压缩率比 Gzip 要低;Hadoop 本身不支持,需要安装。

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

5. 压缩位置选择

压缩可以在MapReduce作用的任意阶段启用,如图所示:

在这里插入图片描述

MapReduce 数据压缩

6. 压缩参数配置

要在Hadoop中启用压缩,可以配置如下参数:

参数 默认值 阶段 建议
io.compression.codecs (在core-site.xml中配置) org.apache.hadoop.io.compress.DefaultCodec, org.apache.hadoop.io.compress.GzipCodec, org.apache.hadoop.io.compress.BZip2Codec 输入压缩 Hadoop使用文件扩展名判断是否支持某种编解码器
mapreduce.map.output.compress(在mapred-site.xml中配置) false mapper输出 这个参数设为true启用压缩
mapreduce.map.output.compress.codec(在mapred-site.xml中配置) org.apache.hadoop.io.compress.DefaultCodec mapper输出 企业多使用LZO或Snappy编解码器在此阶段压缩数据
mapreduce.output.fileoutputformat.compress(在mapred-site.xml中配置) false reducer输出 这个参数设为true启用压缩
mapreduce.output.fileoutputformat.compress.codec(在mapred-site.xml中配置) org.apache.hadoop.io.compress. DefaultCodec reducer输出 使用标准工具或者编解码器,如gzip和bzip2
mapreduce.output.fileoutputformat.compress.type(在mapred-site.xml中配置) RECORD reducer输出 SequenceFile输出使用的压缩类型:NONE和BLOCK
配置参数

7. 压缩实操案例

7.1 数据流的压缩和解压缩

CompressionCodec有两个方法可以用于轻松的压缩活解压缩数据。

  • 压缩:需要对数据流的数据进行压缩,我们可以使用 createOutputStream(OutputStreamout)方法创建一个 CompressionOutputStream。将其压缩,写入底层的流。
  • 解压缩:需要对输入流读取来的数据进行解压缩,则需要调用 createInputStream(InputStreamin)函数,从而获得一个 CompressionInputStream,从底层的流读取解压缩的数据。

7.2 测试 Hadoop 压缩方式

DEFLATE org.apache.hadoop.io.compress.DefaultCodec
gzip org.apache.hadoop.io.compress.GzipCodec
bzip2 org.apache.hadoop.io.compress.BZip2Codec
package com.bigdata.hadoop.mapreduce.compress;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.compress.*;
import org.apache.hadoop.util.ReflectionUtils;

public class TestCompress {
    
    

    public static void main(String[] args) throws Exception {
    
    
        String method = BZip2Codec.class.getSimpleName();
        compress("/data.txt",method);
		decompress("/data.txt.bz2");
    }

    // 1、压缩
    private static void compress(String filename, String method) throws Exception {
    
    

        // (1)获取输入流
        FileInputStream fis = new FileInputStream(new File(filename));

        Class codecClass = Class.forName(method);

        CompressionCodec codec = (CompressionCodec) ReflectionUtils.newInstance(codecClass, new Configuration());

        // (2)获取输出流
        FileOutputStream fos = new FileOutputStream(new File(filename + codec.getDefaultExtension()));
        CompressionOutputStream cos = codec.createOutputStream(fos);

        // (3)流的对拷
        IOUtils.copyBytes(fis, cos, 1024*1024*5, false);

        // (4)关闭资源
        cos.close();
        fos.close();
        fis.close();
    }

    // 2、解压缩
    private static void decompress(String filename) throws FileNotFoundException, IOException {
    
    

        // (0)校验是否能解压缩
        CompressionCodecFactory factory = new CompressionCodecFactory(new Configuration());

        CompressionCodec codec = factory.getCodec(new Path(filename));

        if (codec == null) {
    
    
            System.out.println("cannot find codec for file " + filename);
            return;
        }

        // (1)获取输入流
        CompressionInputStream cis = codec.createInputStream(new FileInputStream(new File(filename)));

        // (2)获取输出流
        FileOutputStream fos = new FileOutputStream(new File(filename + ".decoded"));

        // (3)流的对拷
        IOUtils.copyBytes(cis, fos, 1024*1024*5, false);

        // (4)关闭资源
        cis.close();
        fos.close();
    }
}

7.3 Map 输出端采用压缩

即使你的MapReduce的输入输出文件都是未压缩的文件,你仍然可以对Map任务的中间结果输出做压缩,因为它要写在硬盘并且通过网络传输到Reduce节点,对其压缩可以提高很多性能,这些工作只要设置两个属性即可,我们来看下代码怎么设置。

Hadoop 源码给大家提供的压缩格式有:BZip2CodecDefaultCodec

  1. Mapper保持不变

    package com.bigdata.hadoop.mapreduce.compress;
    
    import org.apache.hadoop.io.IntWritable;
    import org.apache.hadoop.io.LongWritable;
    import org.apache.hadoop.io.Text;
    import org.apache.hadoop.mapreduce.Mapper;
    
    import java.io.IOException;
    
    public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable>{
          
          
    
        Text k = new Text();
        IntWritable v = new IntWritable(1);
    
        @Override
        protected void map(LongWritable key, Text value, Context context)throws IOException, InterruptedException {
          
          
    
            // 1 获取一行
            String line = value.toString();
    
            // 2 切割
            String[] words = line.split(" ");
    
            // 3 循环写出
            for(String word:words){
          
          
                k.set(word);
                context.write(k, v);
            }
        }
    }
    
  2. Reducer 保持不变

    package com.bigdata.hadoop.mapreduce.compress;
    
    import org.apache.hadoop.io.IntWritable;
    import org.apache.hadoop.io.Text;
    import org.apache.hadoop.mapreduce.Reducer;
    
    import java.io.IOException;
    
    public class WordCountReducer extends Reducer<Text, IntWritable, Text, IntWritable>{
          
          
    
        IntWritable v = new IntWritable();
    
        @Override
        protected void reduce(Text key, Iterable<IntWritable> values,
                              Context context) throws IOException, InterruptedException {
          
          
    
            int sum = 0;
    
            // 1 汇总
            for(IntWritable value:values){
          
          
                sum += value.get();
            }
    
            v.set(sum);
    
            // 2 输出
            context.write(key, v);
        }
    }
    
  3. Driver 端添加压缩方式

    package com.bigdata.hadoop.mapreduce.compress;
    
    import java.io.IOException;
    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.io.compress.CompressionCodec;
    import org.apache.hadoop.io.compress.GzipCodec;
    import org.apache.hadoop.mapreduce.Job;
    import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
    import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
    
    public class WordCountDriver {
          
          
    
        public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
          
          
    
            Configuration configuration = new Configuration();
    
            // 开启map端输出压缩
            configuration.setBoolean("mapreduce.map.output.compress", true);
            // 设置map端输出压缩方式
            configuration.setClass("mapreduce.map.output.compress.codec", BZip2Codec.class, CompressionCodec.class);
    
            Job job = Job.getInstance(configuration);
    
            job.setJarByClass(WordCountDriver.class);
    
            job.setMapperClass(WordCountMapper.class);
            job.setReducerClass(WordCountReducer.class);
    
            job.setMapOutputKeyClass(Text.class);
            job.setMapOutputValueClass(IntWritable.class);
    
            job.setOutputKeyClass(Text.class);
            job.setOutputValueClass(IntWritable.class);
    
            FileInputFormat.setInputPaths(job, new Path(args[0]));
            FileOutputFormat.setOutputPath(job, new Path(args[1]));
    
            boolean result = job.waitForCompletion(true);
    
            System.exit(result ? 1 : 0);
        }
    }
    

7.4 Reduce 输出端采用压缩

Mapper和Reducer保持不变(详见7.3)

基于WordCount案例处理。

package com.bigdata.hadoop.mapreduce.compress;

import java.io.IOException;
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.io.compress.CompressionCodec;
import org.apache.hadoop.io.compress.GzipCodec;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

public class WordCountDriver {
    
    

    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
    
    

        Configuration configuration = new Configuration();

        // 开启map端输出压缩
        configuration.setBoolean("mapreduce.map.output.compress", true);
        // 设置map端输出压缩方式
        configuration.setClass("mapreduce.map.output.compress.codec", BZip2Codec.class, CompressionCodec.class);

        Job job = Job.getInstance(configuration);

        job.setJarByClass(WordCountDriver.class);

        job.setMapperClass(WordCountMapper.class);
        job.setReducerClass(WordCountReducer.class);

        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(IntWritable.class);

        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);

        FileInputFormat.setInputPaths(job, new Path(args[0]));
        FileOutputFormat.setOutputPath(job, new Path(args[1]));
        // 设置reduce端输出压缩开启
        FileOutputFormat.setCompressOutput(job, true);

        // 设置压缩的方式
        FileOutputFormat.setOutputCompressorClass(job, BZip2Codec.class);

        boolean result = job.waitForCompletion(true);

        System.exit(result ? 1 : 0);
    }
}

猜你喜欢

转载自blog.csdn.net/dwjf321/article/details/110197733