ORC文件了解


今天才知道,我之所以漂泊就是在向你靠近

一、ORC File文件介紹

ORC是列式存儲格式,為了降低存儲空間和加速查詢速度①。根據行組分割整個表,根據行組分割整個表②。自描述的,它的元數據使用Protocol Buffers序列化,並且文件中的數據盡可能的壓縮以降低存儲空間的消耗③。
被Spark SQL、Presto等支持,Impala對於ORC目前沒有支持,使用Parquet作為主要的列式存儲格式④。
1.列式存儲,多種文件壓縮方式,高壓縮比
2.文件是可切分(Split)節省HDFS存儲,查詢任務的輸入數據量減少,使用的MapTask也就減少了
3.提供了多種索引,row group index、bloom filter index
4.可以支持復雜的數據結構(比如Map等)

列式存儲

OLAP特點,列式存儲可以提升其性能。對比關系型數據庫中的行式存儲,列式存儲每一列的所有元素都是順序存儲的。由此有以下優化:
 不需要全量掃描,只需涉及的列,將I/O降低了N倍。可以保存每一列的統計信息(min、max、sum等),實現部分的謂詞下推
每一列成員同構,可以有針對的使用壓縮算法,進一步減少I/O
同構性,使用更加合適CPU pipeline的編碼方式,減少CPU緩存失效

ORC官方文檔:
https://cwiki.apache.org/confluence/display/Hive/LanguageManual+ORC
說明:orc在壓縮和解壓縮的時候會消耗少量的cpu

數據模型

與parquet不同,orc不支持嵌套結構,通過特殊處理來替代
CREATE TABLE orcStructTable( name string,
course structcourse:string,score:int,
score map<string,int>,
work_locations array )
結構分為復雜類型和原始類型。復雜類型:LIST、STRUCT、MAP和UNION
原始類型:BOOLEAN、整數、浮點數、字符串類型等
Struct可以有讀個孩子節點、map包含key和value、list包含成員類型的孩子節點。每一個Schema樹的根節點為一個Struct類型,
所有的column按照樹的中序遍歷順序編號。

Orc只需存儲schema樹中葉子節點值,中間非葉子節點是一層代理,它們輔助葉子節點值得讀取。
葉子節點才會讀取數據,然后交給父節點封裝返回。

文件結構

Parquet類似,ORC文件以二進制方式存儲,是不可以直接讀取,ORC文件也是自解析的,它包含許多的元數據,
這些元數據都是同構ProtoBuffer進行序列化的。ORC涉及到如下的概念:
ORC文件:普通二進制文件,一個ORC文件中可以包含多個stripe,每一個stripe包含多條記錄,這些記錄按照列進行獨立存儲,對應到Parquet中的row group的概念。
文件級元數據:包括文件的描述信息PostScript、文件meta信息(包括整個文件的統計信息)、所有stripe的信息和文件schema信息。
stripe:一組行形成一個stripe,每次讀取文件是以行組為單位的,一般為HDFS的塊大小,保存了每一列的索引和數據。
stripe元數據:保存stripe的位置、每一個列的在該stripe的統計信息以及所有的stream類型和位置
row group:索引的最小單位,一個stripe中包含多個row group,默認為10000個值組成。
stream:一個stream表示文件中一段有效的數據,包括索引和數據兩類。索引stream保存每一個row group的位置和統計信息,數據stream包括多種類型的數據,
具體需要哪幾種是由該列類型和編碼方式決定。

在ORC文件中保存了三個層級的統計信息,分別為文件級別、stripe級別和row group級別的,他們都可以用來根據Search ARGuments(謂詞下推條件)
判斷是否可以跳過某些數據,在統計信息中都包含成員數和是否有null值,並且對於不同類型的數據設置一些特定的統計信息。
(1)file level
在ORC文件的末尾會記錄文件級別的統計信息,會記錄整個文件中columns的統計信息。這些信息主要用於查詢的優化,也可以為一些簡單的聚合查詢比如max, min, sum輸出結果。
(2)stripe level
ORC文件會保存每個字段stripe級別的統計信息,ORC reader使用這些統計信息來確定對於一個查詢語句來說,需要讀入哪些stripe中的記錄。
比如說某個stripe的字段max(a)=10,min(a)=3,那么當where條件為a >10或者a <3時,那么這個stripe中的所有記錄在查詢語句執行時不會被讀入。 
(3)row level 
為了進一步的避免讀入不必要的數據,在邏輯上將一個column的index以一個給定的值(默認為10000,可由參數配置)分割為多個index組。
以10000條記錄為一個組,對數據進行統計。Hive查詢引擎會將where條件中的約束傳遞給ORC reader,這些reader根據組級別的統計信息,過濾掉不必要的數據。
如果該值設置的太小,就會保存更多的統計信息,用戶需要根據自己數據的特點權衡一個合理的值。

數據訪問

讀取ORC文件是從尾部開始的,第一次讀取16KB的大小,盡可能的將Postscript和Footer數據都讀入內存。文件的最后一個字節保存着PostScript的長度,它的長度不會超過256字節,
PostScript中保存着整個文件的元數據信息,它包括文件的壓縮格式、文件內部每一個壓縮塊的最大長度(每次分配內存的大小)、Footer長度,以及一些版本信息。
在Postscript和Footer之間存儲着整個文件的統計信息(上圖中未畫出),這部分的統計信息包括每一個stripe中每一列的信息,主要統計成員數、最大值、最小值、是否有空值等。
接下來讀取文件的Footer信息,它包含了每一個stripe的長度和偏移量,該文件的schema信息(將schema樹按照schema中的編號保存在數組中)、整個文件的統計信息以及每一個row group的行數。
處理stripe時首先從Footer中獲取每一個stripe的其實位置和長度、每一個stripe的Footer數據(元數據,記錄了index和data的的長度),整個striper被分為index和data兩部分,
stripe內部是按照row group進行分塊的(每一個row group中多少條記錄在文件的Footer中存儲),row group內部按列存儲。每一個row group由多個stream保存數據和索引信息。
每一個stream的數據會根據該列的類型使用特定的壓縮算法保存。在ORC中存在如下幾種stream類型:
PRESENT:每一個成員值在這個stream中保持一位(bit)用於標示該值是否為NULL,通過它可以只記錄部位NULL的值
DATA:該列的中屬於當前stripe的成員值。
LENGTH:每一個成員的長度,這個是針對string類型的列才有的。
DICTIONARY_DATA:對string類型數據編碼之后字典的內容。
SECONDARY:存儲Decimal、timestamp類型的小數或者納秒數等。
ROW_INDEX:保存stripe中每一個row group的統計信息和每一個row group起始位置信息。

在初始化階段獲取全部的元數據之后,可以通過includes數組指定需要讀取的列編號,它是一個boolean數組,如果不指定則讀取全部的列,還可以通過傳遞SearchArgument參數指定過濾條件,
根據元數據首先讀取每一個stripe中的index信息,然后根據index中統計信息以及SearchArgument參數確定需要讀取的row group編號,再根據includes數據決定需要從這些row group中讀取的列,
通過這兩層的過濾需要讀取的數據只是整個stripe多個小段的區間,然后ORC會盡可能合並多個離散的區間盡可能的減少I/O次數。然后再根據index中保存的下一個row group的位置信息調至該stripe中第一個需要讀取的row group中。

ORC文件格式只支持讀取指定字段,還不支持只讀取特殊字段類型中的指定部分。 
使用ORC文件格式時,用戶可以使用HDFS的每一個block存儲ORC文件的一個stripe。對於一個ORC文件來說,stripe的大小一般需要設置得比HDFS的block小,如果不這樣的話,
一個stripe就會分別在HDFS的多個block上,當讀取這種數據時就會發生遠程讀數據的行為。如果設置stripe的只保存在一個block上的話,如果當前block上的剩余空間不足以存儲下一個strpie,
ORC的writer接下來會將數據打散保存在block剩余的空間上,直到這個block存滿為止。這樣,下一個stripe又會從下一個block開始存儲。
由於ORC中使用了更加精確的索引信息,使得在讀取數據時可以指定從任意一行開始讀取,更細粒度的統計信息使得讀取ORC文件跳過整個row group,ORC默認會對任何一塊數據和索引信息使用ZLIB壓縮,
因此ORC文件占用的存儲空間也更小,這點在后面的測試對比中也有所印證。

文件壓縮

ORC文件使用兩級壓縮機制,首先將一個數據流使用流式編碼器進行編碼,然后使用一個可選的壓縮器對數據流進行進一步壓縮。 
一個column可能保存在一個或多個數據流中,可以將數據流划分為以下四種類型: 
• Byte Stream 
字節流保存一系列的字節數據,不對數據進行編碼。 
• Run Length Byte Stream 
字節長度字節流保存一系列的字節數據,對於相同的字節,保存這個重復值以及該值在字節流中出現的位置。 
• Integer Stream 
整形數據流保存一系列整形數據。可以對數據量進行字節長度編碼以及delta編碼。具體使用哪種編碼方式需要根據整形流中的子序列模式來確定。 
• Bit Field Stream 
比特流主要用來保存boolean值組成的序列,一個字節代表一個boolean值,在比特流的底層是用Run Length Byte Stream來實現的。

接下來會以Integer和String類型的字段舉例來說明。

(1)Integer 
對於一個整形字段,會同時使用一個比特流和整形流。比特流用於標識某個值是否為null,整形流用於保存該整形字段非空記錄的整數值。 
(2)String 
對於一個String類型字段,ORC writer在開始時會檢查該字段值中不同的內容數占非空記錄總數的百分比不超過0.8的話,就使用字典編碼,字段值會保存在一個比特流,一個字節流及兩個整形流中。
比特流也是用於標識null值的,字節流用於存儲字典值,一個整形流用於存儲字典中每個詞條的長度,另一個整形流用於記錄字段值。 
如果不能用字典編碼,ORC writer會知道這個字段的重復值太少,用字典編碼效率不高,ORC writer會使用一個字節流保存String字段的值,然后用一個整形流來保存每個字段的字節長度。 
在ORC文件中,在各種數據流的底層,用戶可以自選ZLIB, Snappy和LZO壓縮方式對數據流進行壓縮。編碼器一般會將一個數據流壓縮成一個個小的壓縮單元,在目前的實現中,壓縮單元的默認大小是256KB。

二、Hive+ORC建立數據倉庫

在建Hive表的時候我們就應該指定文件的存儲格式。所以你可以在Hive QL語句里面指定用ORCFile這種文件格式,如下:

CREATE TABLE ... STORED AS ORC

ALTER TABLE ... [PARTITION partition_spec] SET FILEFORMAT ORC

SET hive.default.fileformat=Orc

所有關於ORCFile的參數都是在Hive QL語句的TBLPROPERTIES字段里面出現,他們是:

三、Java操作ORC

到https://orc.apache.org官網下載orc源碼包,然后編譯獲取orc-core-1.3.0.jar、orc-mapreduce-1.3.0.jar、orc-tools-1.3.0.jar,將其加入項目中

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hive.ql.exec.vector.LongColumnVector;
import org.apache.hadoop.hive.ql.exec.vector.VectorizedRowBatch;
import org.apache.orc.CompressionKind;
import org.apache.orc.OrcFile;
import org.apache.orc.TypeDescription;
import org.apache.orc.Writer;

public class TestORCWriter {

    public static void main(String[] args) throws Exception {
        Path testFilePath = new Path("/tmp/test.orc");
        Configuration conf = new Configuration();
        TypeDescription schema = TypeDescription.fromString("struct<field1:int,field2:int,field3:int>");
        Writer writer = OrcFile.createWriter(testFilePath, OrcFile.writerOptions(conf).setSchema(schema).compress(CompressionKind.SNAPPY));
        VectorizedRowBatch batch = schema.createRowBatch();
        LongColumnVector first = (LongColumnVector) batch.cols[0];
        LongColumnVector second = (LongColumnVector) batch.cols[1];
        LongColumnVector third = (LongColumnVector) batch.cols[2];

        final int BATCH_SIZE = batch.getMaxSize();
        // add 1500 rows to file
        for (int r = 0; r < 15000000; ++r) {
            int row = batch.size++;
            first.vector[row] = r;
            second.vector[row] = r * 3;
            third.vector[row] = r * 6;
            if (row == BATCH_SIZE - 1) {
                writer.addRowBatch(batch);
                batch.reset();
            }
        }
        if (batch.size != 0) {
            writer.addRowBatch(batch);
            batch.reset();
        }
        writer.close();
    }
}
參考:

http://lxw1234.com/archives/2016/04/630.htm

https://www.iteblog.com/archives/1014.html

http://blog.csdn.net/dabokele/article/details/51542327

http://blog.csdn.net/dabokele/article/details/51813322

http://blog.csdn.net/nysyxxg/article/details/52241848

http://blog.csdn.net/yu616568/article/details/51868447


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM