最近項目需要調研了下orcfile文件的格式、hive執行流程、hactalog等,整理和大家分享下,歡迎拍磚和探討 。
廢話少說,第一篇orcfile
Orcfile
一些優點
Orcfile存儲格式
各部分結構
Ø 整個文件的存儲中,row data是自定義的編碼,其余的都是使用protobuf進行編碼成byte[],然后存儲的,均為結構化數據。
Ø 最后一個字節是postscript的長度,也決定了post的長度不超過255(unsigned byte)
Ø Postscript數據類型
主要信息 :file footer的長度、文件壓縮類型、orcfile版本以及magic等信息 。
Ø File footer ,整個文件的概述
主要信息:一些orcfile的參數、orcfile文件的column類型、key-value個人信息、Stripe信息以及每個stripe的各個數據部分數據長度信息..
Ø 待補充
每個stripe的結構
Ø Stripe腳(StripeFooter)
這個stripe中的stream的概要信息
RowIndex,同樣采用protobuf的格式來存儲
data信息,采用自己編碼的方式來存儲 ,data的分類
紅色的為index的信息,黑色的都為data中的不同類型stream
Present:bit數組,針對null值的優化
Data:具體的data信息
Length:字符串的長度
dictionary_data:字典編碼的字典
secondary:針對於timestamp的優化
Ø Index的統計信息
Ø 待補充
orcfile的讀寫
orcfile的寫
Ø 創建write方法
Orcfile的配置項:
http://docs.hortonworks.com/HDPDocuments/HDP2/HDP-2.0.0.2/ds_Hive/orcfile.html#ORCFiles-HiveQLSyntax
最終寫key-value數據的wrieter,和rcfile、squence等file一樣key為null
Ø 關於OrcSerdeRow,通過OrcSerde的initialize方法,或者自己創建:
private Object realRow;
private ObjectInspector inspector;
兩個變量,realRow真實的一行,是一個writable[]類型,里面存儲的是各個列的值 。
Inspector是一個OrcStructInspector類型,里面存儲這每個filed的ObjectInspector(hive中的一個概念,我現在的理解是將數據交換用的類型轉換為Java的基本類型的一個東東,將存儲和計算中的數值解耦開,個人可以自定義以適應不同的存儲類型,例如writable、avro等類型)
realRow 是 OrcStruct類型,其 private Object[] fields 存儲的是writable類型的數據。
Ø 關於writer:實現的類為WriterImpl,主要的是創建TreeWriter,負責寫各個列的是數據,與其對應的是在讀的時候有個TreeReader,負責具體的各個列數據的讀取
Ø 關於orcfile的TreeWriter:具體負責寫各個列的writer,是一棵樹的結構(以下以table中都是基本類型數據為類),基本的列類型為:int:string:timestampe:short:date:double
IntegerTreeWriter:
byte、short、int、long均轉換為該類型的write,最底層的為一個RunLengthIntegerWriter(V2) stream 。
StringTreeWriter :
一般分為兩種情況:直接寫,寫每個字符串的長度,char數組;使用字典編碼:字典字符串(長度+char數組)、字符在字典中的位置 。
寫的時候兩種流同時寫,最后計算所有已寫的字符串中non-null字符串 和 字典個數的比例,若是超過一點的閥值(比如說1,就是沒有重復的字符串,字典大小和字符串一直)就只寫寫,廢棄字典 。
TimestampTreeWriter:
精確的時間讀寫,將時間分成秒數(linux時間)+好描述,兩個RunLengthInteger Stream數組流,利於壓縮 。
DateTreeWriter:
轉換成linux時間的秒數,最終使用RunLengthInteger Stream來存儲 。
DoubleTreeWriter:
沒有特殊處理,直接轉換成byte數組 。
從低位到高位依次寫入stream 。
Orcfile的寫對於一個stripe數據是全部寫入內存stream buffer 后,在fulsh硬盤上的,對於讀去也是,整個stripe讀取到內存buffer中的 。
其它方式待補充,感興趣可以參考相應的類
orcfile的讀
Ø 創建基本的RecordReader<NullWritable, OrcStruct> ,然后按照hadoop的基本模型,一條條的讀取具體的數據
Split方法直接繼承自fileInputFormat,按照文件和大小兩個因素來拆分 。
關於inputFormat.getRecordReader
然后OrcFile.createReader ,這個方法其實就是讀取一個orcfile文件的PostScript以及file footer信息:一些orcfile的參數、orcfile文件的column類型、key-value個人信息、Stripe信息以及每個stripe的各個數據部分數據長度信息,詳述見上面 。無論文件一個orcfile被拆成基本split(一個map處理,有文件的offset length),但是這部分信息都是讀取的同一個orcfile的文件尾部 。
接着OrcRecordReader的構造函數,利用conf中的hive.io.filter.expr.serialized(謂詞下推的條件數) 和hive.io.file.readcolumn.ids、hive.io.file.readcolumn.names(需要的列),計算出需要讀取的存儲列以及需要過濾掉的行, 構造本次讀取的Stream 。
最終通過OrcRecordReader.nex()讀出來的是OrcStruct ,
fields是所有列存儲數組,實際是writable[]類型 。
關於謂詞下推pushdown ,將謂詞條件數轉換成orcfile的SearchArgument對象 。
SearchArgumentImp的實現中有兩個屬性:
ExpressionTree類 : 一棵謂詞的條件樹 。
PredicateLeaf 類:一個具體的謂詞判斷條件 ,在后續做謂詞下推的判斷條件時候可以將index中的信息帶入這里面,然后得出一個判斷值 TruthValue 。
兩個枚舉類介紹
謂詞表達式符號 :
謂詞判斷邏輯結果類TruthValue :
對於一個完整的SearchArgumentImpl ,demo如下
謂詞邏輯樹是:((prodline_id>100 and st_date>'20130101' ) or ad_src_id in(15 ,20 ,30) or alb_cust_id between 0 and 200000) and contract_line_id > prodline_id ,其數據結結構如下:
RecordReaderImpl. pickRowGroups(),判斷一個stripe中多少個index對應的數據段應該被過濾,得到一個boolean[]result ,若是result[i]==false ,則表示第i個小塊的數據被過濾 。
RecordReaderImpl. readAllDataStreams(),不需要過濾的時候將整個stripe讀取到內存buffer中,使用InStream(orc自定義的一個內存inputStream,里面有)來存儲 。
RecordReaderImpl. readPartialDataStreams() ,讀取過濾后的一些小塊 ,通過index得到的過濾結果,判斷哪些位置的數據需要讀,讀取出來到一個bytebuffer[]中,然后再計算每個column的一系列不連續的塊的offset和endoffset ,然后封裝成InStream ,主要的幾個方法 :
planReadPartialDataStreams():計算每個列的哪些位置數據應該被讀出來;
mergeDiskRanges() :將連續的數據合並起來,讀到一個bytebuffer中;
createStreams(streamList, chunks, bytes, included, codec, bufferSize,streams):根據上面計算的未知信息,將所有數據讀出來並構造需要的InStream ;
demo ,以下數據在一個orcfile中的數據結構(data部分和footer的streams)
id(long) |
Name(string) |
Sex(string) |
Socre(double) |
Address(string) |
14 |
lili |
male |
98.75 |
Street.5 |
15 |
xiaoming |
male |
80.25 |
Null |
16 |
lucy |
fmale |
85.7 |
Street.7 |
25 |
leke |
male |
79 |
Null |
27 |
null |
male |
83.25 |
Street.9.shangdi |
Stripte-data
StripeFooter List<Stream> :index和data中的stream
Col號 |
Stream類型 |
Stream長度(byte[]的長度) |
1 |
ROW_INDEX |
|
…… |
|
|
5 |
ROW_INDEX |
|
1 |
DATA(long,以run-longth存儲) |
|
2 |
PRESENT |
|
2 |
LENGTH(long,每列字符串的char個數) |
|
2 |
DATA(一個個的char) |
|
3 |
LENGTH(long,字段的每個單詞包含的char) |
|
3 |
DICTIONARY_DATA(字典的char集,和上面一個構成字典) |
|
3 |
DATA(long,一個field在字典中的未知) |
|
4 |
DATA(double,以64位定長編碼double) |
|
5 |
PRESENT |
|
5 |
LENGTH(long,每列字符串的char個數) |
|
5 |
DATA(一個個的char) |
|
Data |
|
|
|
14 15 16 25 27 |
11110 |
4 8 4 4 |
Lilixiaominglucyleke |
3 4 |
malefmale |
0 0 1 0 0 |
98.75 80.25 85.7 79 83.25 |
10101 |
7 7 15 |
Street.5Street.7Street.9.shangdi |
測試數據結果,在總共4200萬行的數據中過濾選擇,數據大小在3G左右
測試分為兩種:
一:按照某一列排序,然后按照這列的條件來做過濾,這樣就會出現的是從某一個index開始后的所有數據被連續讀出來,避免不斷的隨機尋址
二 :修改部分orc謂詞下推的代碼,在所有的index中隨機間隔的選出一個作為命中塊,這樣會出現數據過濾了一下,但是會出現多次磁盤尋址在讀取(有的可能是讀取很小的數據段)的情況,具體測試如下:
Row num |
Cost(s) |
Read buffer count |
順序讀數據 |
||
42064498 |
117 |
47 |
40634498 |
112 |
46 |
38559498 |
106 |
43 |
36024498 |
103 |
41 |
31924498 |
88 |
36 |
27399498 |
75 |
31 |
22034498 |
63 |
25 |
14359498 |
41 |
16 |
12209498 |
35 |
14 |
隨機讀數據 |
||
42064498 |
118 |
47 |
37929498 |
122 |
171 |
33454498 |
121 |
401 |
29215000 |
122 |
637 |
25189498 |
116 |
877 |
21449498 |
110 |
1090 |
16725000 |
96 |
1291 |
13030000 |
84 |
1382 |
8354498 |
66 |
1411 |
4130000 |
45 |
1303 |
后續使用OrcFile的一些改進想法
1、 在保證計算性能的前提下,擴展orcfile的統計信息,用作table數據統計畫像和改進謂詞下推過濾。
2、 在擴展統計信息的基礎上改進謂詞下推的算法.
3、 可以參考infobright的<<Infobright_–_Analytic_Database_Engine>>和<Brighthouse_An_Analytic_Data_Warehouse_for_Ad-hoc_Queries>論文以及infobright的一些查詢優化 .
4、 針對hdfs的block大小,來控制stripe文件大小,以盡量保證local .
5、 待補充.
相關的ppt
http://files.cnblogs.com/serendipity/Orcfile%E7%AE%80%E4%BB%8B.pptx