一、ORC File文件結構 ORC的全稱是(Optimized Row Columnar),ORC文件格式是一種Hadoop生態圈中的列式存儲格式,它的產生早在2013年初,最初產生自Apache Hive,用於降低Hadoop數據存儲空間和加速Hive查詢速度。和Parquet類似,它並不是一個 ...
一、ORC File文件結構
ORC的全稱是(Optimized Row Columnar),ORC文件格式是一種Hadoop生態圈中的列式存儲格式,它的產生早在2013年初,最初產生自Apache Hive,用於降低Hadoop數據存儲空間和加速Hive查詢速度。和Parquet類似,它並不是一個單純的列式存儲格式,仍然是首先根據行組分割整個表,在每一個行組內進行按列存儲。ORC文件是自描述的,它的元數據使用Protocol Buffers序列化,並且文件中的數據儘可能的壓縮以降低存儲空間的消耗,目前也被Spark SQL、Presto等查詢引擎支持,但是Impala對於ORC目前沒有支持,仍然使用Parquet作為主要的列式存儲格式。2015年ORC項目被Apache項目基金會提升為Apache頂級項目。ORC具有以下一些優勢:
- ORC是列式存儲,有多種文件壓縮方式,並且有著很高的壓縮比。
- 文件是可切分(Split)的。因此,在Hive中使用ORC作為表的文件存儲格式,不僅節省HDFS存儲資源,查詢任務的輸入數據量減少,使用的MapTask也就減少了。
- 提供了多種索引,row group index、bloom filter index。
- ORC可以支持複雜的數據結構(比如Map等)
列式存儲
由於OLAP查詢的特點,列式存儲可以提升其查詢性能,但是它是如何做到的呢?這就要從列式存儲的原理說起,從圖1中可以看到,相對於關係資料庫中通常使用的行式存儲,在使用列式存儲時每一列的所有元素都是順序存儲的。由此特點可以給查詢帶來如下的優化:
- 查詢的時候不需要掃描全部的數據,而只需要讀取每次查詢涉及的列,這樣可以將I/O消耗降低N倍,另外可以保存每一列的統計信息(min、max、sum等),實現部分的謂詞下推。
- 由於每一列的成員都是同構的,可以針對不同的數據類型使用更高效的數據壓縮演算法,進一步減小I/O。
- 由於每一列的成員的同構性,可以使用更加適合CPU pipeline的編碼方式,減小CPU的緩存失效。
關於Orc文件格式的官網介紹,見:
https://cwiki.apache.org/confluence/display/Hive/LanguageManual+ORC
需要註意的是,ORC在讀寫時候需要消耗額外的CPU資源來壓縮和解壓縮,當然這部分的CPU消耗是非常少的。
數據模型
和Parquet不同,ORC原生是不支持嵌套數據格式的,而是通過對複雜數據類型特殊處理的方式實現嵌套格式的支持,例如對於如下的hive表:
CREATE TABLE `orcStructTable`( `name` string, `course` struct<course:string,score:int>, `score` map<string,int>, `work_locations` array<string> )
在ORC的結構中包含了複雜類型列和原始類型,前者包括LIST、STRUCT、MAP和UNION類型,後者包括BOOLEAN、整數、浮點數、字元串類型等,其中STRUCT的孩子節點包括它的成員變數,可能有多個孩子節點,MAP有兩個孩子節點,分別為key和value,LIST包含一個孩子節點,類型為該LIST的成員類型,UNION一般不怎麼用得到。每一個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元數據
請參考:更高的壓縮比,更好的性能–使用ORC文件格式優化Hive
數據訪問
讀取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文件占用的存儲空間也更小,這點在後面的測試對比中也有所印證。
關於row group index和bloom filter index的性能優化,請參考Hive性能優化之ORC索引–Row Group Index vs Bloom Filter Index
文件壓縮
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欄位裡面出現,他們是:
Key |
Default |
Notes |
orc.compress |
ZLIB |
high level compression (one of NONE, ZLIB, SNAPPY) |
orc.compress.size |
262,144 |
number of bytes in each compression chunk |
orc.stripe.size |
268435456 |
number of bytes in each stripe |
orc.row.index.stride |
10,000 |
number of rows between index entries (must be >= 1000) |
orc.create.index |
true |
whether to create row indexes |
三、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(); } }
大多情況下,還是建議在Hive中將文本文件轉成ORC格式,這種用JAVA在本地生成ORC文件,屬於特殊需求場景。
參考:
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