大數據學習(九)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");
    }
}

這一章比較簡單,如果有錯誤的話還請大家指出。
共勉~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章