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