HDFS文件壓縮和小文件治理

1.文件壓縮

文件壓縮好處:

  • 減少數據所佔用的磁盤空間

  • 加快數據在磁盤、網絡上的IO

Hadoop的壓縮實現類;均實現CompressionCodec接口

壓縮格式 對應的編碼/解碼器
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

 

查看集羣是否支持本地壓縮(所有節點都要確認)

[hadoop@node01 ~]$ hadoop checknative

編程實踐

編程:上傳壓縮過的文件到HDFS

  • 對CopyFileFromLocal代碼做修改,向文件壓縮後,再上傳到HDFS

  • 代碼

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.BZip2Codec;
import org.apache.hadoop.io.compress.CompressionCodec;
import org.apache.hadoop.io.compress.CompressionOutputStream;
import org.apache.hadoop.util.ReflectionUtils;

import java.io.*;
import java.net.URI;

/**
 *
 * 將本地文件系統的文件通過java-API寫入到HDFS文件,並且寫入時使用壓縮
 */
public class CopyFileFromLocal {

    /**
     *
     * @param args 兩個參數 C:\test\01_018分鐘.mp4 hdfs://node01:8020/copyFromLocal/01_018分鐘.bz2
     * @throws ClassNotFoundException
     */
    public static void main(String[] args) throws ClassNotFoundException {

        //壓縮相關
        //壓縮類
        //HDFS讀寫的配置文件
        Configuration conf = new Configuration();
        BZip2Codec codec = new BZip2Codec();
        codec.setConf(conf);

        String source = args[0]; //linux或windows中的文件路徑,demo存在一定數據

        String destination="hdfs://node01:8020/copyFromLocal/01_018分鐘.bz2";//HDFS的路徑

        InputStream in = null;
        try {
            in = new BufferedInputStream(new FileInputStream(source));

            FileSystem fs = FileSystem.get(URI.create(destination),conf);

            //調用Filesystem的create方法返回的是FSDataOutputStream對象
            //該對象不允許在文件中定位,因爲HDFS只允許一個已打開的文件順序寫入或追加
            OutputStream out = fs.create(new Path(destination));
            //對輸出流的數據壓縮
            CompressionOutputStream compressedOut = codec.createOutputStream(out);

            //流拷貝
            IOUtils.copyBytes(in, compressedOut, 4096, true);
        } catch (FileNotFoundException e) {
            System.out.println("exception");
            e.printStackTrace();
        } catch (IOException e) {
            System.out.println("exception1");
            e.printStackTrace();
        }
    }
}

2.小文件治理

HDFS不適合存儲小文件

  • NameNode存儲着文件系統的元數據,每個文件、目錄、塊大概有150字節的元數據;

  • NN內存有限,因此HDFS存儲文件數量的也有上限,如果小文件過多則會造成NN的壓力過大

  • 且HDFS能存儲的數據總量也會變小

Sequence Files方案

  • SequenceFile文件,主要由一條條record記錄組成;

  • 具體結構(如上圖):

    • 一個SequenceFile首先有一個4字節的header(文件版本號)

    • 接着是若干record記錄

    • 每個record是鍵值對形式的;鍵值類型是可序列化類型,如IntWritable、Text

    • 記錄間會隨機的插入一些同步點sync marker,用於方便定位到記錄邊界

  • SequenceFile文件可以作爲小文件的存儲容器;

    • 每條record保存一個小文件的內容

    • 小文件名作爲當前record的鍵;

    • 小文件的內容作爲當前record的值;

    • 如10000個100KB的小文件,可以編寫程序將這些文件放到一個SequenceFile文件。

  • 一個SequenceFile是可分割的,所以MapReduce可將文件切分成塊,每一塊獨立操作。

  • 不像HAR,SequenceFile支持壓縮。記錄的結構取決於是否啓動壓縮

    • 支持兩類壓縮:

      • 不壓縮NONE,如上圖

      • 壓縮RECORD,如上圖

      • 壓縮BLOCK,如下圖,①一次性壓縮多條記錄;②每一個新塊Block開始處都需要插入同步點

    • 在大多數情況下,以block(注意:指的是SequenceFile中的block)爲單位進行壓縮是最好的選擇

    • 因爲一個block包含多條記錄,利用record間的相似性進行壓縮,壓縮效率更高

    • 把已有的數據轉存爲SequenceFile比較慢。比起先寫小文件,再將小文件寫入SequenceFile,一個更好的選擇是直接將數據寫入一個SequenceFile文件,省去小文件作爲中間媒介.

編程向SequenceFile寫入數據

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.IntWritable;
import org.apache.hadoop.io.SequenceFile;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.compress.BZip2Codec;

import java.io.IOException;
import java.net.URI;

public class SequenceFileWriteNewVersion {

    //模擬數據源;數組中一個元素表示一個文件的內容
    private static final String[] DATA = {
            "The Apache Hadoop software library is a framework that allows for the distributed processing of large data sets across clusters of computers using simple programming models.",
            "It is designed to scale up from single servers to thousands of machines, each offering local computation and storage.",
            "Rather than rely on hardware to deliver high-availability, the library itself is designed to detect and handle failures at the application layer",
            "o delivering a highly-available service on top of a cluster of computers, each of which may be prone to failures.",
            "Hadoop Common: The common utilities that support the other Hadoop modules."
    };

    public static void main(String[] args) throws IOException {
        //輸出路徑:要生成的SequenceFile文件名
        String uri = "hdfs://node01:8020/writeSequenceFile";

        Configuration conf = new Configuration();
        FileSystem fs = FileSystem.get(URI.create(uri), conf);
        //向HDFS上的此SequenceFile文件寫數據
        Path path = new Path(uri);

        //因爲SequenceFile每個record是鍵值對的
        //指定key類型
        IntWritable key = new IntWritable(); //key數字 -> int -> IntWritable
        //指定value類型
        Text value = new Text();//value -> String -> Text

        //創建向SequenceFile文件寫入數據時的一些選項
        //要寫入的SequenceFile的路徑
        SequenceFile.Writer.Option pathOption       = SequenceFile.Writer.file(path);
        //record的key類型選項
        SequenceFile.Writer.Option keyOption        = SequenceFile.Writer.keyClass(IntWritable.class);
        //record的value類型選項
        SequenceFile.Writer.Option valueOption      = SequenceFile.Writer.valueClass(Text.class);
        //SequenceFile壓縮方式:NONE | RECORD | BLOCK三選一
        //方案一:RECORD、不指定壓縮算法
//        SequenceFile.Writer.Option compressOption   = SequenceFile.Writer.compression(SequenceFile.CompressionType.RECORD);
//        SequenceFile.Writer writer = SequenceFile.createWriter(conf, pathOption, keyOption, valueOption, compressOption);


        //方案二:BLOCK、不指定壓縮算法
//        SequenceFile.Writer.Option compressOption   = SequenceFile.Writer.compression(SequenceFile.CompressionType.BLOCK);
//        SequenceFile.Writer writer = SequenceFile.createWriter(conf, pathOption, keyOption, valueOption, compressOption);



        //方案三:使用BLOCK、壓縮算法BZip2Codec;壓縮耗時間
        //再加壓縮算法
        BZip2Codec codec = new BZip2Codec();
        codec.setConf(conf);
        SequenceFile.Writer.Option compressAlgorithm = SequenceFile.Writer.compression(SequenceFile.CompressionType.RECORD, codec);
        //創建寫數據的Writer實例
        SequenceFile.Writer writer = SequenceFile.createWriter(conf, pathOption, keyOption, valueOption, compressAlgorithm);

        for (int i = 0; i < 100000; i++) {
            //分別設置key、value值
            key.set(100000 - i);
            value.set(DATA[i % DATA.length]); //%取模 3 % 3 = 0;
            System.out.printf("[%s]\t%s\t%s\n", writer.getLength(), key, value);
            //在SequenceFile末尾追加內容
            writer.append(key, value);
        }
        //關閉流
        IOUtils.closeStream(writer);
    }
}

編程讀取SequenceFile文件

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.SequenceFile;
import org.apache.hadoop.io.Writable;
import org.apache.hadoop.util.ReflectionUtils;

import java.io.IOException;

public class SequenceFileReadNewVersion {

    public static void main(String[] args) throws IOException {
        //要讀的SequenceFile
        String uri = "hdfs://node01:8020/writeSequenceFile";
        Configuration conf = new Configuration();
        Path path = new Path(uri);

        //Reader對象
        SequenceFile.Reader reader = null;
        try {
            //讀取SequenceFile的Reader的路徑選項
            SequenceFile.Reader.Option pathOption = SequenceFile.Reader.file(path);

            //實例化Reader對象
            reader = new SequenceFile.Reader(conf, pathOption);

            //根據反射,求出key類型對象
            Writable key = (Writable)
                    ReflectionUtils.newInstance(reader.getKeyClass(), conf);
            //根據反射,求出value類型對象
            Writable value = (Writable)
                    ReflectionUtils.newInstance(reader.getValueClass(), conf);

            long position = reader.getPosition();
            System.out.println(position);

            while (reader.next(key, value)) {
                String syncSeen = reader.syncSeen() ? "*" : "";
                System.out.printf("[%s%s]\t%s\t%s\n", position, syncSeen, key, value);
                //移動到下一個record開頭的位置
                position = reader.getPosition(); // beginning of next record
            }
        } finally {
            IOUtils.closeStream(reader);
        }
    }
}

 

發佈了60 篇原創文章 · 獲贊 68 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章