本文主要轉至:http://www.cnblogs.com/skyl/html
Hadoop 做爲MR 的開源實現,一直以動態運行解析文件格式並得到比MPP數據庫快上幾倍的裝載速度爲優點。不過,MPP數據庫社區也一直批評Hadoop因爲文件格式並不是爲特定目的而建,所以序列化和反序列化的成本太高。算法
文本格式的數據也是Hadoop中常常碰到的。如TextFile 、XML和JSON。 文本格式除了會佔用更多磁盤資源外,對它的解析開銷通常會比二進制格式高几十倍以上,尤爲是XML 和JSON,它們的解析開銷比Textfile 還要大,所以強烈不建議在生產系統中使用這些格式進行儲存。 若是須要輸出這些格式,請在客戶端作相應的轉換操做。 文本格式常常會用於日誌收集,數據庫導入,Hive默認配置也是使用文本格式,並且經常容易忘了壓縮,因此請確保使用了正確的格式。另外文本格式的一個缺點是它不具有類型和模式,好比銷售金額、利潤這類數值數據或者日期時間類型的數據,若是使用文本格式保存,因爲它們自己的字符串類型的長短不一,或者含有負數,致使MR沒有辦法排序,因此每每須要將它們預處理成含有模式的二進制格式,這又致使了沒必要要的預處理步驟的開銷和儲存資源的浪費。數據庫
1.TextFileapache
--建立數據表: 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; --插入數據: set hive.exec.compress.output=true; --啓用壓縮格式 set mapred.output.compress=true; set mapred.output.compression.codec=org.apache.hadoop.io.compress.GzipCodec; --指定輸出的壓縮格式爲Gzip set io.compression.codecs=org.apache.hadoop.io.compress.GzipCodec; insert overwrite table textfile_table select * from T_Name;
2.SequenceFilejson
值得注意的是,hive讀取sequencefile的時候,是把key忽略的,也就是直接讀value而且按照指定分隔符分隔字段。可是若是hive的數據來源是從mr生成的,那麼寫sequencefile的時候,key和value都是有意義的,key不能被忽略,而是應該當成第一個字段。爲了解決這種不匹配的狀況,有兩種辦法。一種是要求凡是結果會給hive用的mr job輸出value的時候帶上key。可是這樣的話對於開發是一個負擔,讀寫數據的時候都要注意這個狀況。因此更好的方法是第二種,也就是把這個源自於hive的問題交給hive解決,寫一個InputFormat包裝一下,把value輸出加上key便可。緩存
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; --插入數據操做: set hive.exec.compress.output=true; --啓用輸出壓縮格式 set mapred.output.compress=true; set mapred.output.compression.codec=org.apache.hadoop.io.compress.GzipCodec; --指定輸出壓縮格式爲Gzip set io.compression.codecs=org.apache.hadoop.io.compress.GzipCodec; SET mapred.output.compression.type=BLOCK; --指定爲Block insert overwrite table seqfile_table select * from T_Name;
3.RCFile架構
BG:大多數的Hadoop和Hive存儲都是行式存儲的,在大多數場景下,這是比較高效的。這種高效歸根於以下幾點:大多數的表具備的字段個數都不大(通常小於20個);對文件按塊進行壓縮對於須要處理重複數據的狀況比較高效,同時不少的處理和調試工具(如:more,head,awk)均可以很好的應用於行式存儲的數據。併發
並不是全部的工具和數據存儲都是採用行式存儲的方式的,對於特定類型的數據和應用來講,採用列式存儲有時會更好。例如,若是指定的表具備成百上千個字段,而大多數的查詢只須要使用到其中的一小部分字段,這是掃描全部的行而過濾掉大部分的數據顯然是個浪費。然而,若是數據是按照列而不是行進行存儲的話,那麼只要對其須要的列進行掃描就能夠了,這樣能夠提升性能。app
對於列式存儲而言,進行壓縮一般會很是高效,特別是在這列的數據具備較低計算的時候(只有不多的排重值時)。同時,一些列式存儲並不須要無力存儲NULL值的列。框架
基於這些場景,HIVE中才設計了RCFile.
Hive功能強大的一個方面體如今不一樣的存儲格式間相互轉換數據很是的簡單。存儲信息存放在了表的元數據信息中。當對錶執行一個select查詢時。以及向其餘表中執行insert操做時,Hive就會使用這個表的元數據信息中提供的內容,而後自動執行轉換過程。這樣使用能夠有多種選擇,而不須要額外的程序來對不一樣的存儲格式進行轉換。
能夠在建立表時使用:ColumnarSerDe, RCFileInputFormat 和 RCFileOutputFormat; /(end bg)
存儲方式:數據按行分塊,每塊按列存儲。結合了行存儲和列存儲的優勢:
RCFile的一個行組包括三個部分:
數據追加:RCFile 不支持任意方式的數據寫操做,僅提供一種追加接口,這是由於底層的 HDFS當前僅僅支持數據追加寫文件尾部。
行組大小:行組變大有助於提升數據壓縮的效率,可是可能會損害數據的讀取性能,由於這樣增長了 Lazy 解壓性能的消耗。並且行組變大會佔用更多的內存,這會影響併發執行的其餘MR做業。 考慮到存儲空間和查詢效率兩個方面,Facebook 選擇 4MB 做爲默認的行組大小,固然也容許用戶自行選擇參數進行配置。
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; --插入數據操做: 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 rcfile_table select * from T_Name;
4.ORCFile
相比傳統數據庫的行式存儲引擎,列式存儲引擎具備更高的壓縮比,更少的IO操做,尤爲是在數據列不少,但每次操做僅針對若干列進行查詢和計算的情景,列式存儲引擎的性價比更高。目前在開源實現中,最有名的列式存儲引擎莫過於Parquet和ORC,而且他們都是Apache的頂級項目,在數據存儲引擎方面發揮着重要的做用。
自定義格式
ORC(OptimizedRow Columnar) 文件格式存儲源自於RC(RecordColumnar File)這種存儲格式,RC是一種列式存儲引擎,對schema演化(修改schema須要從新生成數據)支持較差,而ORC是對RC改進,但它仍對schema演化支持較差,主要是在壓縮編碼,查詢性能方面作了優化。RC/ORC最初是在Hive中獲得使用,最後發展勢頭不錯,獨立成一個單獨的項目。Hive 1.x版本對事務和update操做的支持,即是基於ORC實現的(其餘存儲格式暫不支持)。ORC發展到今天,已經具有一些很是高級的feature,好比支持update操做,支持ACID,支持struct,array複雜類型。你可使用複雜類型構建一個相似於parquet的嵌套式數據架構,但當層數很是多時,寫起來很是麻煩和複雜,而parquet提供的schema表達方式更容易表示出多級嵌套的數據類型。
Hive中建立表時使用ORC數據存儲格式:
create table orc_table (id int,name string) stored as orc;
hive> create table myfile_table(str STRING) > stored as > inputformat 'org.apache.hadoop.hive.contrib.fileformat.base64.Base64TextInputFormat' > outputformat 'org.apache.hadoop.hive.contrib.fileformat.base64.Base64TextOutputFormat'; OK Time taken: 0.399 seconds
hive> load data local inpath '/root/hive/myfile_table' > overwrite into table myfile_table;--加載數據
hive> dfs -text /user/hive/warehouse/myfile_table/myfile_table;--數據文件內容,編碼後的格式 aGVsbG8saGl2ZQ== aGVsbG8sd29ybGQ= aGVsbG8saGFkb29w
hive> select * from myfile_table;--使用自定義格式進行解碼 OK hello,hive hello,world hello,hadoop Time taken: 0.117 seconds, Fetched: 3 row(s)
5.Parquet列式存儲格式 (這部分徹底轉至:http://www.infoq.com/cn/articles/in-depth-analysis-of-parquet-column-storage-format/)
Parquet是面向分析型業務的列式存儲格式,由Twitter和Cloudera合做開發,2015年5月從Apache的孵化器裏畢業成爲Apache頂級項目,最新的版本是1.8.0。
列式存儲和行式存儲相比有哪些優點呢?
當時Twitter的日增數據量達到壓縮以後的100TB+,存儲在HDFS上,工程師會使用多種計算框架(例如MapReduce, Hive, Pig等)對這些數據作分析和挖掘;日誌結構是複雜的嵌套數據類型,例如一個典型的日誌的schema有87列,嵌套了7層。因此須要設計一種列式存儲格式,既能支持關係型數據(簡單數據類型),又能支持複雜的嵌套類型的數據,同時可以適配多種數據處理框架。
關係型數據的列式存儲,能夠將每一列的值直接排列下來,不用引入其餘的概念,也不會丟失數據。關係型數據的列式存儲比較好理解,而嵌套類型數據的列存儲則會遇到一些麻煩。如圖1所示,咱們把嵌套數據類型的一行叫作一個記錄(record),嵌套數據類型的特色是一個record中的column除了能夠是Int, Long, String這樣的原語(primitive)類型之外,還能夠是List, Map, Set這樣的複雜類型。在行式存儲中一行的多列是連續的寫在一塊兒的,在列式存儲中數據按列分開存儲,例如能夠只讀取A.B.C這一列的數據而不去讀A.E和A.B.D,那麼如何根據讀取出來的各個列的數據重構出一行記錄呢?
圖1 行式存儲和列式存儲
Google的Dremel系統解決了這個問題,核心思想是使用「record shredding and assembly algorithm」來表示複雜的嵌套數據類型,同時輔以按列的高效壓縮和編碼技術,實現下降存儲空間,提升IO效率,下降上層應用延遲。Parquet就是基於Dremel的數據模型和算法實現的。
Parquet適配多種計算框架
Parquet是語言無關的,並且不與任何一種數據處理框架綁定在一塊兒,適配多種語言和組件,可以與Parquet配合的組件有:
查詢引擎: Hive, Impala, Pig, Presto, Drill, Tajo, HAWQ, IBM Big SQL
計算框架: MapReduce, Spark, Cascading, Crunch, Scalding, Kite
數據模型: Avro, Thrift, Protocol Buffers, POJOs
那麼Parquet是如何與這些組件協做的呢?這個能夠經過圖2來講明。數據從內存到Parquet文件或者反過來的過程主要由如下三個部分組成:
1, 存儲格式(storage format)
parquet-format項目定義了Parquet內部的數據類型、存儲格式等。
2, 對象模型轉換器(object model converters)
這部分功能由parquet-mr項目來實現,主要完成外部對象模型與Parquet內部數據類型的映射。
3, 對象模型(object models)
對象模型能夠簡單理解爲內存中的數據表示,Avro, Thrift, Protocol Buffers, Hive SerDe, Pig Tuple, Spark SQL InternalRow等這些都是對象模型。Parquet也提供了一個example object model 幫助你們理解。
例如parquet-mr項目裏的parquet-pig項目就是負責把內存中的Pig Tuple序列化並按列存儲成Parquet格式,以及反過來把Parquet文件的數據反序列化成Pig Tuple。
這裏須要注意的是Avro, Thrift, Protocol Buffers都有他們本身的存儲格式,可是Parquet並無使用他們,而是使用了本身在parquet-format項目裏定義的存儲格式。因此若是你的應用使用了Avro等對象模型,這些數據序列化到磁盤仍是使用的parquet-mr定義的轉換器把他們轉換成Parquet本身的存儲格式。
圖2 Parquet項目的結構
Parquet數據模型
理解Parquet首先要理解這個列存儲格式的數據模型。咱們以一個下面這樣的schema和數據爲例來講明這個問題。
message AddressBook { required string owner; repeated string ownerPhoneNumbers; repeated group contacts { required string name; optional string phoneNumber; } }
這個schema中每條記錄表示一我的的AddressBook。有且只有一個owner,owner能夠有0個或者多個ownerPhoneNumbers,owner能夠有0個或者多個contacts。每一個contact有且只有一個name,這個contact的phoneNumber無關緊要。這個schema能夠用圖3的樹結構來表示。
每一個schema的結構是這樣的:根叫作message,message包含多個fields。每一個field包含三個屬性:repetition, type, name。repetition能夠是如下三種:required(出現1次),optional(出現0次或者1次),repeated(出現0次或者屢次)。type能夠是一個group或者一個primitive類型。
Parquet格式的數據類型沒有複雜的Map, List, Set等,而是使用repeated fields 和 groups來表示。例如List和Set能夠被表示成一個repeated field,Map能夠表示成一個包含有key-value 對的repeated field,並且key是required的。
圖3 AddressBook的樹結構表示
Parquet文件的存儲格式
那麼如何把內存中每一個AddressBook對象按照列式存儲格式存儲下來呢?
在Parquet格式的存儲中,一個schema的樹結構有幾個葉子節點,實際的存儲中就會有多少column。例如上面這個schema的數據存儲實際上有四個column,如圖4所示。
圖4 AddressBook實際存儲的列
Parquet文件在磁盤上的分佈狀況如圖5所示。全部的數據被水平切分紅Row group,一個Row group包含這個Row group對應的區間內的全部列的column chunk。一個column chunk負責存儲某一列的數據,這些數據是這一列的Repetition levels, Definition levels和values(詳見後文)。一個column chunk是由Page組成的,Page是壓縮和編碼的單元,對數據模型來講是透明的。一個Parquet文件最後是Footer,存儲了文件的元數據信息和統計信息。Row group是數據讀寫時候的緩存單元,因此推薦設置較大的Row group從而帶來較大的並行度,固然也須要較大的內存空間做爲代價。通常狀況下推薦配置一個Row group大小1G,一個HDFS塊大小1G,一個HDFS文件只含有一個塊。
圖5 Parquet文件格式在磁盤的分佈
拿咱們的這個schema爲例,在任何一個Row group內,會順序存儲四個column chunk。這四個column都是string類型。這個時候Parquet就須要把內存中的AddressBook對象映射到四個string類型的column中。若是讀取磁盤上的4個column要可以恢復出AddressBook對象。這就用到了咱們前面提到的 「record shredding and assembly algorithm」。
Striping/Assembly算法
對於嵌套數據類型,咱們除了存儲數據的value以外還須要兩個變量Repetition Level(R), Definition Level(D) 才能存儲其完整的信息用於序列化和反序列化嵌套數據類型。Repetition Level和 Definition Level能夠說是爲了支持嵌套類型而設計的,可是它一樣適用於簡單數據類型。在Parquet中咱們只需定義和存儲schema的葉子節點所在列的Repetition Level和Definition Level。
Definition Level
嵌套數據類型的特色是有些field能夠是空的,也就是沒有定義。若是一個field是定義的,那麼它的全部的父節點都是被定義的。從根節點開始遍歷,當某一個field的路徑上的節點開始是空的時候咱們記錄下當前的深度做爲這個field的Definition Level。若是一個field的Definition Level等於這個field的最大Definition Level就說明這個field是有數據的。對於required類型的field必須是有定義的,因此這個Definition Level是不須要的。在關係型數據中,optional類型的field被編碼成0表示空和1表示非空(或者反之)。
Repetition Level
記錄該field的值是在哪個深度上重複的。只有repeated類型的field須要Repetition Level,optional 和 required類型的不須要。Repetition Level = 0 表示開始一個新的record。在關係型數據中,repetion level老是0。
下面用AddressBook的例子來講明Striping和assembly的過程。
對於每一個column的最大的Repetion Level和 Definition Level如圖6所示。
圖6 AddressBook的Max Definition Level和Max Repetition Level
下面這樣兩條record:
AddressBook { owner: "Julien Le Dem", ownerPhoneNumbers: "555 123 4567", ownerPhoneNumbers: "555 666 1337", contacts: { name: "Dmitriy Ryaboy", phoneNumber: "555 987 6543", }, contacts: { name: "Chris Aniszczyk" } } AddressBook { owner: "A. Nonymous" }
以contacts.phoneNumber這一列爲例,"555 987 6543"這個contacts.phoneNumber的Definition Level是最大Definition Level=2。而若是一個contact沒有phoneNumber,那麼它的Definition Level就是1。若是連contact都沒有,那麼它的Definition Level就是0。
下面咱們拿掉其餘三個column只看contacts.phoneNumber這個column,把上面的兩條record簡化成下面的樣子:
AddressBook { contacts: { phoneNumber: "555 987 6543" } contacts: { } } AddressBook { }
這兩條記錄的序列化過程如圖7所示:
圖7 一條記錄的序列化過程
若是咱們要把這個column寫到磁盤上,磁盤上會寫入這樣的數據(圖8):
圖8 一條記錄的磁盤存儲
注意:NULL實際上不會被存儲,若是一個column value的Definition Level小於該column最大Definition Level的話,那麼就表示這是一個空值。
下面是從磁盤上讀取數據並反序列化成AddressBook對象的過程:
1,讀取第一個三元組R=0, D=2, Value=」555 987 6543」
R=0 表示是一個新的record,要根據schema建立一個新的nested record直到Definition Level=2。
D=2 說明Definition Level=Max Definition Level,那麼這個Value就是contacts.phoneNumber這一列的值,賦值操做contacts.phoneNumber=」555 987 6543」。
2,讀取第二個三元組 R=1, D=1
R=1 表示不是一個新的record,是上一個record中一個新的contacts。
D=1 表示contacts定義了,可是contacts的下一個級別也就是phoneNumber沒有被定義,因此建立一個空的contacts。
3,讀取第三個三元組 R=0, D=0
R=0 表示一個新的record,根據schema建立一個新的nested record直到Definition Level=0,也就是建立一個AddressBook根節點。
能夠看出在Parquet列式存儲中,對於一個schema的全部葉子節點會被當成column存儲,並且葉子節點必定是primitive類型的數據。對於這樣一個primitive類型的數據會衍生出三個sub columns (R, D, Value),也就是從邏輯上看除了數據自己之外會存儲大量的Definition Level和Repetition Level。那麼這些Definition Level和Repetition Level是否會帶來額外的存儲開銷呢?實際上這部分額外的存儲開銷是能夠忽略的。由於對於一個schema來講level都是有上限的,並且非repeated類型的field不須要Repetition Level,required類型的field不須要Definition Level,也能夠縮短這個上限。例如對於Twitter的7層嵌套的schema來講,只須要3個bits就能夠表示這兩個Level了。
對於存儲關係型的record,record中的元素都是非空的(NOT NULL in SQL)。Repetion Level和Definition Level都是0,因此這兩個sub column就徹底不須要存儲了。因此在存儲非嵌套類型的時候,Parquet格式也是同樣高效的。
上面演示了一個column的寫入和重構,那麼在不一樣column之間是怎麼跳轉的呢,這裏用到了有限狀態機的知識,詳細介紹能夠參考Dremel。
數據壓縮算法
列式存儲給數據壓縮也提供了更大的發揮空間,除了咱們常見的snappy, gzip等壓縮方法之外,因爲列式存儲同一列的數據類型是一致的,因此可使用更多的壓縮算法。
壓縮算法 |
使用場景 |
Run Length Encoding |
重複數據 |
Delta Encoding |
有序數據集,例如timestamp,自動生成的ID,以及監控的各類metrics |
Dictionary Encoding |
小規模的數據集合,例如IP地址 |
Prefix Encoding |
Delta Encoding for strings |
性能
Parquet列式存儲帶來的性能上的提升在業內已經獲得了充分的承認,特別是當大家的表很是寬(column很是多)的時候,Parquet不管在資源利用率仍是性能上都優點明顯。具體的性能指標詳見參考文檔。
Spark已經將Parquet設爲默認的文件存儲格式,Cloudera投入了不少工程師到Impala+Parquet相關開發中,Hive/Pig都原生支持Parquet。Parquet如今爲Twitter至少節省了1/3的存儲空間,同時節省了大量的表掃描和反序列化的時間。這兩方面直接反應就是節約成本和提升性能。
若是說HDFS是大數據時代文件系統的事實標準的話,Parquet就是大數據時代存儲格式的事實標準。
Apache Parquet 最初的設計動機是存儲嵌套式數據,好比Protocolbuffer,thrift,json等,將這類數據存儲成列式格式,以方便對其高效壓縮和編碼,且使用更少的IO操做取出須要的數據,這也是Parquet相比於ORC的優點,它可以透明地將Protobuf和thrift類型的數據進行列式存儲,在Protobuf和thrift被普遍使用的今天,與parquet進行集成,是一件非容易和天然的事情。 除了上述優點外,相比於ORC, Parquet沒有太多其餘可圈可點的地方,好比它不支持update操做(數據寫成後不可修改),不支持ACID等。
6. Avro
Avro是一種用於支持數據密集型的二進制文件格式。它的文件格式更爲緊湊,若要讀取大量數據時,Avro可以提供更好的序列化和反序列化性能。而且Avro數據文件天生是帶Schema定義的,因此它不須要開發者在API 級別實現本身的Writable對象。最近多個Hadoop 子項目都支持Avro數據格式,如Pig 、Hive、Flume、Sqoop和Hcatalog。
7.示例自定義輸入格式:DualInputFormat;
總結:
數據倉庫的特色:一次寫入、屢次讀取,所以,總體來看,ORCFile相比其餘格式具備較明顯的優點。