大数据学习(九)mapreduce数据压缩 二次排序

数据压缩简介

  • 压缩技术能够有效减少底层存储系统(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");
    }
}

这一章比较简单,如果有错误的话还请大家指出。
共勉~

发布了31 篇原创文章 · 获赞 33 · 访问量 2834

猜你喜欢

转载自blog.csdn.net/qq_40742298/article/details/104064289