目錄 概述 hive文件存儲(chǔ)格式包括以下幾類 一、TEXTFILE 二、SEQUENCEFILE 三、RCFile文件格式 概述歷史 RCFile使用 基于行存儲(chǔ)的優(yōu)點(diǎn)和缺點(diǎn) 基于列存儲(chǔ)的優(yōu)點(diǎn)和缺點(diǎn) 源碼分析 1. Writer 2. append RCFile的索引機(jī)制 flushRecords的具體邏輯 RCFile的Sync機(jī)制 RCFileclose過程 數(shù)據(jù)讀取和Lazy解壓 行組大小 四、ORC文件格式 ORC File格式的優(yōu)點(diǎn) 設(shè)計(jì)思想 Stripe結(jié)構(gòu) Hive里面如何用ORCFile 五、Parquet文件格式 概述 Parquet數(shù)據(jù)模型 Parquet文件結(jié)構(gòu) Definition Level Repetition Level Metadata
概述1. hive文件存儲(chǔ)格式包括以下幾類:- TEXTFILE
- SEQUENCEFILE
- RCFILE
- ORCFILE
- Parquet
其中TEXTFILE為默認(rèn)格式,建表時(shí)不指定默認(rèn)為這個(gè)格式,導(dǎo)入數(shù)據(jù)時(shí)會(huì)直接把數(shù)據(jù)文件拷貝到hdfs上不進(jìn)行處理。 sequencefile,rcfile,orcfile格式的表不能直接從本地文件導(dǎo)入數(shù)據(jù),數(shù)據(jù)要先導(dǎo)入到textfile格式的表中, 然后再從表中用insert導(dǎo)入sequencefile,rcfile,orcfile表中。 一、TEXTFILE默認(rèn)格式,數(shù)據(jù)不做壓縮,磁盤開銷大,數(shù)據(jù)解析開銷大。 可結(jié)合Gzip、Bzip2使用(系統(tǒng)自動(dòng)檢查,執(zhí)行查詢時(shí)自動(dòng)解壓),但使用這種方式,hive不會(huì)對(duì)數(shù)據(jù)進(jìn)行切分,從而無法對(duì)數(shù)據(jù)進(jìn)行并行操作。 示例: create table if not exists textfile_table( site string, url string, pv bigint, label string) row format delimited fields terminated by '\t' stored as textfile;
插入數(shù)據(jù)操作: set hive.exec.compress.output=true; set mapred.output.compress=true; set mapred.output.compression.codec=org.apache.hadoop.io.compress.GzipCodec; set io.compression.codecs=org.apache.hadoop.io.compress.GzipCodec;
insert overwrite table textfile_table select * from textfile_table; 二、SEQUENCEFILESequenceFile是Hadoop API提供的一種二進(jìn)制文件支持,其具有使用方便、可分割、可壓縮的特點(diǎn)。 SequenceFile支持三種壓縮選擇:none,record,block。Record壓縮率低,一般建議使用BLOCK壓縮。 示例: create table if not exists seqfile_table( site string, url string, pv bigint, label string) row format delimited fields terminated by '\t' stored as sequencefile;
插入數(shù)據(jù)操作: set hive.exec.compress.output=true; set mapred.output.compress=true; set mapred.output.compression.codec=org.apache.hadoop.io.compress.GzipCodec; set io.compression.codecs=org.apache.hadoop.io.compress.GzipCodec; set mapred.output.compression.type=BLOCK;
insert overwrite table seqfile_table select * from textfile_table; 三、RCFile文件格式概述歷史- RCFile全稱Record Columnar File,列式記錄文件,是一種類似于SequenceFile的鍵值對(duì)(Key/Value Pairs)數(shù)據(jù)文件。
- 在當(dāng)前的基于Hadoop系統(tǒng)的數(shù)據(jù)倉庫中,數(shù)據(jù)存儲(chǔ)格式是影響數(shù)據(jù)倉庫性能的一個(gè)重要因素。Facebook于是提出了集行存儲(chǔ)和列存儲(chǔ)的優(yōu)點(diǎn)于一身的RCFile文件存儲(chǔ)格式。
- 為了提高存儲(chǔ)空間利用率,F(xiàn)acebook各產(chǎn)品線應(yīng)用產(chǎn)生的數(shù)據(jù)從2010年起均采用RCFile結(jié)構(gòu)存儲(chǔ),按行存儲(chǔ)(SequenceFile/TextFile)結(jié)構(gòu)保存的數(shù)據(jù)集也轉(zhuǎn)存為RCFile格式。
- 此外,Yahoo公司也在Pig數(shù)據(jù)分析系統(tǒng)中集成了RCFile,RCFile正在用于另一個(gè)基于Hadoop的數(shù)據(jù)管理系統(tǒng)Howl(http://wiki./pig/Howl)。
- 而且,根據(jù)Hive開發(fā)社區(qū)的交流,RCFile也成功整合加入其他基于MapReduce的數(shù)據(jù)分析平臺(tái)。有理由相信,作為數(shù)據(jù)存儲(chǔ)標(biāo)準(zhǔn)的RCFile,將繼續(xù)在MapReduce環(huán)境下的大規(guī)模數(shù)據(jù)分析中扮演重要角色。
RCFile使用create table if not exists rcfile_table( site string, url string, pv bigint, label string) row format delimited fields terminated by '\t' stored as rcfile;
基于行存儲(chǔ)的優(yōu)點(diǎn)和缺點(diǎn)下圖為Hadoop block中的基于行存儲(chǔ)的示例圖 優(yōu)點(diǎn)是:具備快速數(shù)據(jù)加載和動(dòng)態(tài)負(fù)載的高適應(yīng)能力,因?yàn)樾写鎯?chǔ)保證了相同記錄的所有域都在同一個(gè)集群節(jié)點(diǎn) 缺點(diǎn)是:但是它不太滿足快速的查詢響應(yīng)時(shí)間的要求,特別是在當(dāng)查詢僅僅針對(duì)所有列中的少數(shù)幾列時(shí),它就不能直接定位到所需列而跳過不需要的列,由于混合著不同數(shù)據(jù)值的列,行存儲(chǔ)不易獲得一個(gè)極高的壓縮比。 基于列存儲(chǔ)的優(yōu)點(diǎn)和缺點(diǎn)下圖為Hadoop block中的基于列存儲(chǔ)的示例圖 優(yōu)點(diǎn)是:這種結(jié)構(gòu)使得在查詢時(shí)能夠直接讀取需要的列而避免不必要列的讀取,并且對(duì)于相似數(shù)據(jù)也可以有一個(gè)更好的壓縮比。 缺點(diǎn)是:它并不能提供基于Hadoop系統(tǒng)的快速查詢處理,也不能保證同一記錄的所有列都存儲(chǔ)在同一集群節(jié)點(diǎn)之上,也不適應(yīng)高度動(dòng)態(tài)的數(shù)據(jù)負(fù)載模式。
RCFile設(shè)計(jì)思想 RCFile結(jié)合列存儲(chǔ)和行存儲(chǔ)的優(yōu)缺點(diǎn),F(xiàn)acebook于是提出了基于行列混合存儲(chǔ)的RCFile,該存儲(chǔ)結(jié)構(gòu)遵循的是“先水平劃分,再垂直劃分”的設(shè)計(jì)理念。先將數(shù)據(jù)按行水平劃分為行組,這樣一行的數(shù)據(jù)就可以保證存儲(chǔ)在同一個(gè)集群節(jié)點(diǎn);然后在對(duì)行進(jìn)行垂直劃分。 RCFile是在Hadoop HDFS之上的存儲(chǔ)結(jié)構(gòu),該結(jié)構(gòu)強(qiáng)調(diào): - RCFile存儲(chǔ)的表是水平劃分的,分為多個(gè)行組,每個(gè)行組再被垂直劃分,以便每列單獨(dú)存儲(chǔ);
- RCFile在每個(gè)行組中利用一個(gè)列維度的數(shù)據(jù)壓縮,并提供一種Lazy解壓(decompression)技術(shù)來在查詢執(zhí)行時(shí)避免不必要的列解壓;
- RCFile支持彈性的行組大小,行組大小需要權(quán)衡數(shù)據(jù)壓縮性能和查詢性能兩方面。
- RCFile的每個(gè)行組中,元數(shù)據(jù)頭部和表格數(shù)據(jù)段(每個(gè)列被獨(dú)立壓縮)分別進(jìn)行壓縮,RCFile使用重量級(jí)的Gzip壓縮算法,是為了獲得較好的壓縮比。另外在由于Lazy壓縮策略,當(dāng)處理一個(gè)行組時(shí),RCFile只需要解壓使用到的列,因此相對(duì)較高的Gzip解壓開銷可以減少。
- RCFile具備相當(dāng)于行存儲(chǔ)的數(shù)據(jù)加載速度和負(fù)載適應(yīng)能力,在讀數(shù)據(jù)時(shí)可以在掃描表格時(shí)避免不必要的列讀取,它比其他結(jié)構(gòu)擁有更好的性能,使用列維度的壓縮能夠有效提升存儲(chǔ)空間利用率。
源碼分析通常而言,RCFile文件的整個(gè)寫入過程大致可以分為三步: - 構(gòu)建RCFile.Writer實(shí)例——Writer(...)
- 通過RCFile.Writer實(shí)例寫入數(shù)據(jù)——append
- 關(guān)閉RCFile.Writer實(shí)例——close
我們也按照這三步來分析相應(yīng)的源碼。 1. WriterWriter在構(gòu)建函數(shù)中大體做了以下三件事情: 1)初始化一些變量值; a. RECORD_INTERVAL:表示多少“行”數(shù)據(jù)形成一個(gè)Row Split(Record)和columnsBufferSize配合使用; b. columnNumber:表示當(dāng)前RCFile文件存儲(chǔ)著多少“列”的數(shù)據(jù); c. Metadata:Metadata實(shí)例僅僅保存一個(gè)屬性“hive.io.rcfile.column.number”,值為columnNumber,該實(shí)例會(huì)被序列化到RCFile文件頭部; d. columnsBufferSize:緩存數(shù)目(行數(shù))上限閥值,超過這個(gè)數(shù)值就會(huì)將緩存的數(shù)據(jù)(行)形成一個(gè)Row Split(Record); 2)構(gòu)建一些數(shù)據(jù)結(jié)構(gòu); a. columnValuePlainLength:保存著一個(gè)Row Split(Record)內(nèi)部各列原始數(shù)據(jù)的大?。?br>b. columnBuffers:保存著一個(gè)Row Split(Record)內(nèi)部各列原始數(shù)據(jù); c. key:保存著一個(gè)Row Split(Record)的元數(shù)據(jù); d. plainTotalColumnLength:保存著一個(gè)RCFile文件內(nèi)各列原始數(shù)據(jù)的大?。?br>e. comprTotalColumnLength:保存著一個(gè)RCFile文件內(nèi)各列原始數(shù)據(jù)被壓縮后的大??; 3)初始化文件輸出流,并寫入文件頭部信息; a. 初始化RCFile文件輸出流(FSDataOutputStream);useNewMagic默認(rèn)值為true,本文也以此默認(rèn)值進(jìn)行討論。 b. initializeFileHeader;1. 寫出MAGIC;2. 寫出當(dāng)前RCFile版本號(hào)(不同版本的RCFile具有不同的格式); c. writeFileHeader;1. 寫出是否使用壓縮,本文按使用壓縮討論;2. 寫出壓縮編/解碼器(CompressionCodec)類名;3. 序列化Metadata實(shí)例; d. finalizeFileHeader; 寫出一個(gè)“同步標(biāo)志位”,表示RCFile文件頭部信息到此結(jié)束。 我們可以得出RCFile Header的結(jié)構(gòu)如下: version | 3 bytes of magic header “RCF”, followed by 1 byte of actual version number | compression | A boolean which specifies if compression is turned on for keys/values in this file | compression codec | CompressionCodec class which is used for compression of keys and/or values | metadata | Metadata for this file | sync | A sync marker to denote end of the header |
2. appendRCFile.Writer寫入數(shù)據(jù)時(shí)要求以BytesRefArrayWritable實(shí)例的形式進(jìn)行“追加”,亦即一個(gè)BytesRefArrayWritable實(shí)例表示一“行”數(shù)據(jù)。 “追加”“行”數(shù)據(jù)的過程如下: 1)從一“行”數(shù)據(jù)(即BytesRefArrayWritable實(shí)例val)中解析出各“列”數(shù)據(jù)緩存到對(duì)應(yīng)的ColumnBuffer(即columnBuffers[i])中;如果這“行”數(shù)據(jù)包含的“列”小于columnNumber,則缺失的列會(huì)被填充為“空值”(即BytesRefWritable.ZeroBytesRefWritable); 我們可以看出,RCFile在“追加”數(shù)據(jù)的時(shí)候還是以“行”的方式進(jìn)行,“行轉(zhuǎn)列”是在內(nèi)部進(jìn)行轉(zhuǎn)換的。轉(zhuǎn)換之后的列數(shù)據(jù)(列數(shù)為columnNumber)被緩存到各自的“Buffer”中,也就是說每一列都有自己獨(dú)立的緩存區(qū)(ColumnBuffer),這是為后來的“列式存儲(chǔ)”作準(zhǔn)備的。 ColumnBuffer 這里重點(diǎn)介紹一下這個(gè)ColumnBuffer,它的作用就是用來緩存“列數(shù)據(jù)”的, 內(nèi)部包含兩個(gè)實(shí)例變量,如它們的變量名稱所言,它們實(shí)際也是用來緩存數(shù)據(jù)的,columnValBuffer用來緩存“列值”的數(shù)據(jù),valLenBuffer用來緩存“列值”各自的長度,這兩個(gè)內(nèi)部的緩存區(qū)都是NonSyncDataOutputBuffer實(shí)例。
從這三部分代碼可以看出,NonSyncDataOutputBuffer內(nèi)部的緩存區(qū)實(shí)際是使用內(nèi)存中的一個(gè)字節(jié)數(shù)組(buf)構(gòu)建的,而且繼承自DataOutputStream,方便我們使用“流”的形式操作數(shù)據(jù)。而且valLenBuffer在緩存“列值”的長度的時(shí)候,為了有效的節(jié)約存儲(chǔ)空間,使用了一個(gè)技巧,也就是說,如果需要保存的“列值”長度為“1,1,1,2”,需要存儲(chǔ)四個(gè)整數(shù),而且前面三個(gè)整數(shù)的值是一樣的,那么我們將其變?yōu)椤?,~2,2”,“~2”即表示我們需要將它前面的整數(shù)“1”重復(fù)兩次。如果數(shù)據(jù)的重復(fù)度較高,這種方式會(huì)節(jié)省大量的存儲(chǔ)空間。 RowSplit 2)一“行”數(shù)據(jù)轉(zhuǎn)換為多“列”數(shù)據(jù),并被緩存到各自對(duì)應(yīng)的緩存區(qū)之后,需要進(jìn)行兩個(gè)判斷: - 緩存的“列”數(shù)據(jù)(這里指columnBuffers中的全部列數(shù)據(jù))大小是否超過上限閥值columnsBufferSize?
- 緩存的“行”記錄數(shù)目是否超過上限閥值RECORD_INTERVAL?
如果上述兩者條件滿足其一,我們認(rèn)為已經(jīng)緩存足夠多的數(shù)據(jù),可以將緩存區(qū)的這些數(shù)據(jù)形成一個(gè)Row Split或Record,進(jìn)行“溢寫”。 這兩個(gè)上限閥值(columnsBufferSize、RECORD_INTERVAL)也提示我們?cè)趯?shí)際應(yīng)用中需要根據(jù)實(shí)際情況對(duì)這兩個(gè)值進(jìn)行調(diào)整。 “溢寫”是通過flushRecords進(jìn)行的,可以說是整個(gè)RCFile寫入過程中最為“復(fù)雜”的操作。 前面提到過,RCFile Record(Row Split)實(shí)際是由Key、Value組成的,現(xiàn)在這些“列”數(shù)據(jù)已經(jīng)被緩存到columnBuffers中,那么Key的數(shù)據(jù)在哪里呢? 這個(gè)Key實(shí)際上就是這個(gè)Row Split(Record)的元數(shù)據(jù),也可以理解為Row Split(Record)的索引,它是由KeyBuffer表示的, columnNumber:列數(shù); numberRows:RCFile Record(Row Split)內(nèi)部存儲(chǔ)著多少“行”數(shù)據(jù),同一個(gè)RCFile文件,不同的Record內(nèi)保存的行數(shù)可能不同; RCFile Record Value實(shí)際就是前面提到的columnBuffers中的那些列值(可能經(jīng)過壓縮處理),這些columnBuffers的元數(shù)據(jù)由以下三個(gè)變量表示: - eachColumnValueLen:eachColumnValueLen[i]表示columnBuffers[i]中緩存的列數(shù)據(jù)(原始數(shù)據(jù))的總大?。?/li>
- eachColumnUncompressedValueLen:eachColumnUncompressedValueLen[i]表示columnBuffers[i]中的緩存的列數(shù)據(jù)被壓縮之后的總大??;如果沒有經(jīng)過壓縮處理,該值與columnBuffers[i]相同;
- allCellValLenBuffer:allCellValLenBuffer[i]表示columnBuffers[i]中那些列數(shù)據(jù)各自的長度(注意前方提到的這些長度的保存技巧);
KeyBuffer被序列化之后,它的結(jié)構(gòu)如下: numberRows | Number_of_rows_in_this_record(vint) | columnValueLen | Column_1_ondisk_compressed_length(vint) | columnUncompressedValueLen | Column_1_ondisk_uncompressed_length(vint) | Column_1_row_1_value_plain_length | | Column_1_row_2_value_plain_length | | ... | | columnValueLen | Column_2_ondisk_compressed_length(vint) | columnUncompressedValueLen | Column_2_ondisk_uncompressed_length(vint) | Column_2_row_1_value_plain_length | | Column_2_row_2_value_plain_length | | ... | |
RCFile的索引機(jī)制 注意到上面的多個(gè)columnValueLen(columnUncompressedValueLen),它保存著Record Value內(nèi)多個(gè)列(簇)各自的總長度,而每個(gè)columnValueLen(columnUncompressedValueLen)后面保存著該列(簇)內(nèi)多個(gè)列值各自的長度。如果我們僅僅需要讀取第n列的數(shù)據(jù),我們可以根據(jù)columnValueLen(columnUncompressedValueLen)直接跳過Record Value前面(n - 1)列的數(shù)據(jù)。
KeyBuffer的數(shù)據(jù)是在“溢寫”的過程中被構(gòu)建的。
flushRecords的具體邏輯key是KeyBuffer的實(shí)例,相當(dāng)于在元數(shù)據(jù)中記錄這個(gè)Row Split(Record)的“行數(shù)”; 這段代碼在使用壓縮的場景下才有意義,它構(gòu)建了一個(gè)緩存區(qū)valueBuffer,并且使用“裝飾器”模式構(gòu)建了一個(gè)壓縮輸出流,用于后期將columnBuffers中的數(shù)據(jù)寫入緩存區(qū)valueBuffer,valueBuffer中的數(shù)據(jù)是壓縮過的 接下來就是逐個(gè)處理columnBuffers中的數(shù)據(jù),簡要來說,對(duì)于某個(gè)columnBuffers[i]而言需要做兩件事情: 1)如果使用壓縮,需要將columnBuffers[i]的數(shù)據(jù)通過壓縮輸出流deflateOut寫入valueBuffer中; 2)維護(hù)相關(guān)的幾個(gè)變量值; 這段代碼看似較長,對(duì)于某個(gè)columnBuffers[i]而言,實(shí)際做的事情可以概括為四步: 1)如果使用壓縮,將columnBuffers[i]中的全部數(shù)據(jù)寫入deflateOut(實(shí)際是valueBuffer); 2)記錄columnBuffers[i]經(jīng)過壓縮之后的長度colLen;如果沒有使用使用壓縮,則該值與原始數(shù)據(jù)長度相同; 3)記錄columnBuffers[i]相關(guān)元數(shù)據(jù):columnBuffers[i]壓縮/未壓縮數(shù)據(jù)的長度、columnBuffers[i]中各個(gè)列值的長度; 4)維護(hù)plainTotalColumnLength、comprTotalColumnLength; 代碼至此,一個(gè)Record(Row Split)的所有元數(shù)據(jù)已構(gòu)建完畢;如果啟用壓縮,columnBuffers中的數(shù)據(jù)已全部被壓縮寫入valueBuffer,接下來就是Record Key、Value的“持久化”。
RCFile的Sync機(jī)制比如我們有一個(gè)“大”的文本文件,需要使用MapReduce進(jìn)行分析。Hadoop MapReduce在提交Job之前會(huì)將這個(gè)大的文本文件根據(jù)“切片”大?。僭O(shè)為128M)進(jìn)行“切片”,每一個(gè)MapTask處理這個(gè)文件的一個(gè)“切片”(這里不考慮處理多個(gè)切片的情況),也就是這個(gè)文件的一部分?jǐn)?shù)據(jù)。文本文件是按行進(jìn)行存儲(chǔ)的,那么MapTask從某個(gè)“切片”的起始處讀取文件數(shù)據(jù)時(shí),如何定位一行記錄的起始位置呢? 畢竟“切片”是按照字節(jié)大小直接切分的,很有可能正好將某行記錄“切斷”。這時(shí)就需要有這樣的一個(gè)“sync”,相當(dāng)于一個(gè)標(biāo)志位的作用,讓我們可以識(shí)別一行記錄的起始位置,對(duì)于文本文件而言,這個(gè)“sync”就是換行符。所以,MapTask從某個(gè)“切片”的起始處讀取數(shù)據(jù)時(shí),首先會(huì)“過濾”數(shù)據(jù),直到遇到一個(gè)換行符,然后才開始讀取數(shù)據(jù);如果讀取某行數(shù)據(jù)結(jié)束之后,發(fā)現(xiàn)“文件游標(biāo)”超過該“切片”的范圍,則讀取結(jié)束。
RCFile同樣也需要這樣的一個(gè)“sync”,對(duì)于文本文件而言,是每行文本一個(gè)“sync”;RCFile是以Record為單位進(jìn)行存儲(chǔ)的,但是并沒有每個(gè)Record使用一個(gè)“sync”,而是兩個(gè)“sync”之間有一個(gè)間隔限制SYNC_INTERVAL, SYNC_INTERVAL = 100 * (4 + 16) 每次開始輸出下一個(gè)Record的數(shù)據(jù)之前,都會(huì)計(jì)算當(dāng)前文件的輸出位置相對(duì)于上個(gè)“sync”的偏移量,如果超過SYNC_INTERVAL就輸出一個(gè)“sync”。 ii. write total record length、key portion length iii. write keyLength、keyBuffer 注意這里的keyLength與ii中的keyLength不同:ii中的keyLength相當(dāng)于記錄的是keyBuffer原始數(shù)據(jù)的長度;而iii中的keyLength相當(dāng)于記錄的是keyBuffer原始數(shù)據(jù)被壓縮之后的長度,如果沒有壓縮,該值與ii中的keyLength相同。
代碼至此,我們就完成了一個(gè)Row Split(Record)的輸出。 最后就是清空相關(guān)記錄,為下一個(gè)Row Split(Record)的緩存輸出作準(zhǔn)備, RCFileclose過程RCFile文件的“關(guān)閉”操作大致可分為兩步: 1)如果緩存區(qū)中仍有數(shù)據(jù),調(diào)用flushRecords將數(shù)據(jù)“溢寫”出去; 2)關(guān)閉文件輸出流。 數(shù)據(jù)讀取和Lazy解壓在MapReduce框架中,mapper將順序處理HDFS塊中的每個(gè)行組。當(dāng)處理一個(gè)行組時(shí),RCFile無需全部讀取行組的全部內(nèi)容到內(nèi)存。相反,它僅僅讀元數(shù)據(jù)頭部和給定查詢需要的列。因此,它可以跳過不必要的列以獲得列存儲(chǔ)的I/O優(yōu)勢。(例如,表tbl(c1, c2, c3, c4)有4個(gè)列,做一次查詢“SELECT c1 FROM tbl WHERE c4 = 1”,對(duì)每個(gè)行組,RCFile僅僅讀取c1和c4列的內(nèi)容。).在元數(shù)據(jù)頭部和需要的列數(shù)據(jù)加載到內(nèi)存中后,它們需要解壓。元數(shù)據(jù)頭部總會(huì)解壓并在內(nèi)存中維護(hù)直到RCFile處理下一個(gè)行組。然而,RCFile不會(huì)解壓所有加載的列,相反,它使用一種Lazy解壓技術(shù)。 Lazy解壓意味著列將不會(huì)在內(nèi)存解壓,直到RCFile決定列中數(shù)據(jù)真正對(duì)查詢執(zhí)行有用。由于查詢使用各種WHERE條件,Lazy解壓非常有用。如果一個(gè)WHERE條件不能被行組中的所有記錄滿足,那么RCFile將不會(huì)解壓WHERE條件中不滿足的列。例如,在上述查詢中,所有行組中的列c4都解壓了。然而,對(duì)于一個(gè)行組,如果列c4中沒有值為1的域,那么就無需解壓列c1。 行組大小I/O性能是RCFile關(guān)注的重點(diǎn),因此RCFile需要行組夠大并且大小可變。行組大小和下面幾個(gè)因素相關(guān)。 - 行組大的話,數(shù)據(jù)壓縮效率會(huì)比行組小時(shí)更有效。根據(jù)對(duì)Facebook日常應(yīng)用的觀察,當(dāng)行組大小達(dá)到一個(gè)閾值后,增加行組大小并不能進(jìn)一步增加Gzip算法下的壓縮比。
- 行組變大能夠提升數(shù)據(jù)壓縮效率并減少存儲(chǔ)量。因此,如果對(duì)縮減存儲(chǔ)空間方面有強(qiáng)烈需求,則不建議選擇使用小行組。需要注意的是,當(dāng)行組的大小超過4MB,數(shù)據(jù)的壓縮比將趨于一致。
- 盡管行組變大有助于減少表格的存儲(chǔ)規(guī)模,但是可能會(huì)損害數(shù)據(jù)的讀性能,因?yàn)檫@樣減少了Lazy解壓帶來的性能提升。而且行組變大會(huì)占用更多的內(nèi)存,這會(huì)影響并發(fā)執(zhí)行的其他MapReduce作業(yè)??紤]到存儲(chǔ)空間和查詢效率兩個(gè)方面,F(xiàn)acebook選擇4MB作為默認(rèn)的行組大小,當(dāng)然也允許用戶自行選擇參數(shù)進(jìn)行配置。
四、ORC文件格式ORC File,它的全名是Optimized Row Columnar (ORC) file,其實(shí)就是對(duì)RCFile做了一些優(yōu)化。據(jù)官方文檔介紹,這種文件格式可以提供一種高效的方法來存儲(chǔ)Hive數(shù)據(jù)。它的設(shè)計(jì)目標(biāo)是來克服Hive其他格式的缺陷。運(yùn)用ORC File可以提高Hive的讀、寫以及處理數(shù)據(jù)的性能。 ORC File格式的優(yōu)點(diǎn)- 每個(gè)task只輸出單個(gè)文件,這樣可以減少NameNode的負(fù)載;
- 支持各種復(fù)雜的數(shù)據(jù)類型,比如: datetime, decimal, 以及一些復(fù)雜類型(struct, list, map, and union);
- 在文件中存儲(chǔ)了一些輕量級(jí)的索引數(shù)據(jù);
- 基于數(shù)據(jù)類型的塊模式壓縮:a、integer類型的列用行程長度編碼(run-length encoding);b、String類型的列用字典編碼(dictionary encoding);
- 用多個(gè)互相獨(dú)立的RecordReaders并行讀相同的文件;
- 無需掃描markers就可以分割文件;
- 綁定讀寫所需要的內(nèi)存;
- metadata的存儲(chǔ)是用 Protocol Buffers的,所以它支持添加和刪除一些列
設(shè)計(jì)思想- ORC File包含一組組的行數(shù)據(jù),稱為stripes,
- 除此之外,ORC File的file footer還包含了該ORC File文件中stripes的信息,每個(gè)stripe中有多少行,以及每列的數(shù)據(jù)類型。當(dāng)然,它里面還包含了列級(jí)別的一些聚合的結(jié)果,比如:count, min, max, and sum
- 在ORC File文件的最后,有一個(gè)被稱為postscript的區(qū),它主要是用來存儲(chǔ)壓縮參數(shù)及壓縮頁腳的大小。
- 在默認(rèn)情況下,一個(gè)stripe的大小為250MB。大尺寸的stripes使得從HDFS讀數(shù)據(jù)更高效。
Stripe結(jié)構(gòu)從上圖我們可以看出,每個(gè)Stripe都包含index data、row data以及stripe footer。Stripe footer包含流位置的目錄;Row data在表掃描的時(shí)候會(huì)用到。 Index data包含每列的最大和最小值以及每列所在的行。行索引里面提供了偏移量,它可以跳到正確的壓縮塊位置。具有相對(duì)頻繁的行索引,使得在stripe中快速讀取的過程中可以跳過很多行,盡管這個(gè)stripe的大小很大。在默認(rèn)情況下,最大可以跳過10000行。擁有通過過濾謂詞而跳過大量的行的能力,你可以在表的 secondary keys 進(jìn)行排序,從而可以大幅減少執(zhí)行時(shí)間。比如你的表的主分區(qū)是交易日期,那么你可以對(duì)次分區(qū)(state、zip code以及l(fā)ast name)進(jìn)行排序。 Hive里面如何用ORCFile在建Hive表的時(shí)候我們就應(yīng)該指定文件的存儲(chǔ)格式。所以你可以在Hive QL語句里面指定用ORCFile這種文件格式,如下: - create table ... stored as orc
- alter table ... [partition partition_spec] set fileformat orc
- set hive.default.fileformat=orc
五、Parquet文件格式概述- Apache Parquet是Hadoop生態(tài)圈中一種新型列式存儲(chǔ)格式,它可以兼容Hadoop生態(tài)圈中大多數(shù)計(jì)算框架(Mapreduce、Spark等),被多種查詢引擎支持(Hive、Impala、Drill等),并且它是語言和平臺(tái)無關(guān)的。Parquet最初是由Twitter和Cloudera合作開發(fā)完成并開源,2015年5月從Apache的孵化器里畢業(yè)成為Apache頂級(jí)項(xiàng)目。
- Parquet最初的靈感來自Google于2010年發(fā)表的Dremel論文,文中介紹了一種支持嵌套結(jié)構(gòu)的存儲(chǔ)格式,并且使用了列式存儲(chǔ)的方式提升查詢性能,在Dremel論文中還介紹了Google如何使用這種存儲(chǔ)格式實(shí)現(xiàn)并行查詢的。
Parquet數(shù)據(jù)模型Parquet支持嵌套的數(shù)據(jù)模型,類似于Protocol Buffers,每一個(gè)數(shù)據(jù)模型的schema包含多個(gè)字段,每一個(gè)字段有三個(gè)屬性:重復(fù)次數(shù)、數(shù)據(jù)類型和字段名,重復(fù)次數(shù)可以是以下三種: - required(只出現(xiàn)1次)
- repeated(出現(xiàn)0次或多次)
- optional(出現(xiàn)0次或1次)
每一個(gè)字段的數(shù)據(jù)類型可以分成兩種:group(復(fù)雜類型)和primitive(基本類型) schema示例: 可以把這個(gè)Schema轉(zhuǎn)換成樹狀結(jié)構(gòu),根節(jié)點(diǎn)可以理解為repeated類型,如下圖: - 可以看出在Schema中所有的基本類型字段都是葉子節(jié)點(diǎn),在這個(gè)Schema中一共存在6個(gè)葉子節(jié)點(diǎn),如果把這樣的Schema轉(zhuǎn)換成扁平式的關(guān)系模型,就可以理解為該表包含六個(gè)列。Parquet中沒有Map、Array這樣的復(fù)雜數(shù)據(jù)結(jié)構(gòu),但是可以通過repeated和group組合來實(shí)現(xiàn)的。由于一條記錄中某一列可能出現(xiàn)零次或者多次,需要標(biāo)示出哪些列的值構(gòu)成一條完整的記錄。這是由Striping/Assembly算法實(shí)現(xiàn)的。
- 由于Parquet支持的數(shù)據(jù)模型比較松散,可能一條記錄中存在比較深的嵌套關(guān)系,如果為每一條記錄都維護(hù)一個(gè)類似的樹狀結(jié)可能會(huì)占用較大的存儲(chǔ)空間,因此Dremel論文中提出了一種高效的對(duì)于嵌套數(shù)據(jù)格式的壓縮算法:Striping/Assembly算法。它的原理是每一個(gè)記錄中的每一個(gè)成員值有三部分組成:Value、Repetition level和Definition level。value記錄了該成員的原始值,可以根據(jù)特定類型的壓縮算法進(jìn)行壓縮,兩個(gè)level值用于記錄該值在整個(gè)記錄中的位置。對(duì)于repeated類型的列,Repetition level值記錄了當(dāng)前值屬于哪一條記錄以及它處于該記錄的什么位置;對(duì)于repeated和optional類型的列,可能一條記錄中某一列是沒有值的,假設(shè)我們不記錄這樣的值就會(huì)導(dǎo)致本該屬于下一條記錄的值被當(dāng)做當(dāng)前記錄的一部分,從而造成數(shù)據(jù)的錯(cuò)誤,因此對(duì)于這種情況需要一個(gè)占位符標(biāo)示這種情況。
- 通過Striping/Assembly算法,parquet可以使用較少的存儲(chǔ)空間表示復(fù)雜的嵌套格式,并且通常Repetition level和Definition level都是較小的整數(shù)值,可以通過RLE算法對(duì)其進(jìn)行壓縮,進(jìn)一步降低存儲(chǔ)空間。
Parquet文件結(jié)構(gòu)Parquet文件在磁盤所有數(shù)據(jù)分成多個(gè)RowGroup 和 Footer。 - RowGroup: 真正存儲(chǔ)數(shù)據(jù)區(qū)域,每一個(gè)RowGroup存儲(chǔ)多個(gè)ColumnChunk的數(shù)據(jù)。
- ColumnChunk就代表當(dāng)前RowGroup某一列的數(shù)據(jù),因?yàn)榭赡苓@一列還在其他RowGroup有數(shù)據(jù)。ColumnChunk可能包含一個(gè)Page。
- Page是壓縮和編碼的單元,主要包括PageHeader,RepetitionLevel,DefinitionLevel和Values.
- PageHeader: 包含一些元數(shù)據(jù),諸如編碼和壓縮類型,有多少數(shù)據(jù),當(dāng)前page第一個(gè)數(shù)據(jù)的偏移量,當(dāng)前Page第一個(gè)索引的偏移量,壓縮和解壓的大小
- DefinitionLevel: 當(dāng)前字段在路徑中的深度
- RepetitionLevel: 當(dāng)前字段是否可以重復(fù)
- Footer:主要當(dāng)前文件的元數(shù)據(jù)和一些統(tǒng)計(jì)信息
Definition Level- 指明該列的路徑上有多少個(gè)可選的字段被定義了。A.B.C 表示C列這個(gè)路徑上有三個(gè)可選的字段被定義了。也可以理解為definition Level是該路徑上有定義的repeated field 和optional field的個(gè)數(shù),不包括required field,因?yàn)閞equiredfield是必須有定義的嵌套數(shù)據(jù)的特點(diǎn)是有的字段可以為空,比如optional或者repeated。
- 如果一個(gè)字段被定義,那么它的所有父節(jié)點(diǎn)都是被定義的。我們從root節(jié)點(diǎn)開始遍歷,當(dāng)某一個(gè)字段路徑上的節(jié)點(diǎn)為空或者我們說已經(jīng)沒有子節(jié)點(diǎn)的節(jié)點(diǎn)的時(shí)候,我們就記錄下當(dāng)前的深度作為這個(gè)字段的DefinitionLevel. 當(dāng)一個(gè)字段的DefinitionLevel = Max Definition Level,表示這個(gè)字段是有數(shù)據(jù)的。另外,required類型是字段定義的,所以它不需要DefinitionLevel
messageDemo {--- D = 0 optional group field1 { ----D = 1 required group fiel2 {----D = 1(required是不使用DefinitionLevel的) optional string field3;----D = 2 } } }
Repetition LevelRepetitionLevel是針對(duì)repeated字段的,對(duì)于optional和required,是沒有啥關(guān)系的。意思就是指在哪一個(gè)深度上進(jìn)行重復(fù)。 簡單的說,就是通過數(shù)字讓程序明白在路徑中什么repeated字段重復(fù)了,以此來確定這個(gè)字段的位置 舉個(gè)例子: 我們定一個(gè)Author的數(shù)據(jù)模型: 最后生成的數(shù)據(jù): 分析:AuthorID:因?yàn)樵撟侄问莚equired,必須定義的,所以,它是沒有DefinitionValue,所以都是0 Addresses:因?yàn)樵撟侄问莚epeated,允許0個(gè)或多個(gè)值,所以DefinitionLevel = 1;第一個(gè)Author的第一個(gè)Addresses由于之前沒有重復(fù),是一個(gè)新的record,所以RepetitionLevel = 0; 第二個(gè) Addresses由于在之前已經(jīng)出現(xiàn)過一次,所以它是重復(fù)的,重復(fù)深度是1,所以RepetitionLevel = 1; 到第二Author的時(shí)候,Address是一個(gè)新的record,所以沒有重復(fù),RepetitionLevel = 0,DefinitionLevel = 1 Books.BookID:因?yàn)樵撟侄问莚equired,必須定義的,所以,他沒有DefinitionValue,那么他的DefinitionValue和父父節(jié)點(diǎn)的DefinitionValue相同,DefinitionValue = 1. 因?yàn)锽ooks是Repeated的,但是Books.BookId只出現(xiàn)一次,所以RepetitionLevel = 0。 到第二個(gè)Books.BookId的時(shí)候,由于之前已經(jīng)有過Books,所以現(xiàn)在是重復(fù)的,只是Books重復(fù),所以重復(fù)深度為1,那么此時(shí)RepetitionLevel = 1,DefinitionValue = 1. 到第三個(gè)Books.BookkId的時(shí)候,同樣他也是重復(fù)的,重復(fù)深度也還是1,所以RepetitionLevel = 1,DefinitionValue = 1. Books.Price: 由于price是optional,所以在樹種有兩個(gè)DefinitionLevel=2,由于第一次出現(xiàn)Books.Price,所以RepetitionLevel = 0; 第二個(gè)Books.Price的時(shí)候,DefinitionLevel=2,但是Books已經(jīng)是重復(fù)的,所以現(xiàn)在RepetitionLevel = 1;第三個(gè)沒有Books.Price,所以DefinitionLevel = 1(和Books的DefinitionLevel一樣),RepetitionLevel = 1; Books.Descs.Type:由于是Required,所以DefinitionLevel沒有,和父節(jié)點(diǎn)的DefinitionLevel是一樣的,故DefinitionLevel = 2;第一次出現(xiàn)Books.Descs.Type,所以RepetitionLevel = 0;第二次出現(xiàn)Books.Descs.Type,由于之前已經(jīng)存在了Books.Descs,所以現(xiàn)在他重復(fù)了,Descs重復(fù)深度是2,所以DefinitionLevel = 2, Repetition Level = 2; 下一個(gè)Books.Descs.Type由于沒有Descs,所以DefinitionLevel = 1,Repetition Level只是Books重復(fù),所以深度為1,值為NULL;到下一個(gè)Books.Descs.Type,由于只是Books重復(fù),所以重復(fù)深度為1,DefinitionLevel = 2 Metadata原文鏈接:https://blog.csdn.net/m0_37657725/article/details/98354168
|