在數倉中,建議大家除了接口表(從其他數據庫導入或者是最后要導出到其他數據庫的表),其余表的存儲格式與壓縮格式保持一致。
我們先來說一下目前Hive表主流的存儲格式與壓縮方式。
文件存儲格式
從Hive官網得知,Apache Hive支持Apache Hadoop中使用的幾種熟悉的文件格式,如 TextFile(文本格式)
,RCFile(行列式文件)
,SequenceFile(二進制序列化文件)
,AVRO
,ORC(優化的行列式文件)
和Parquet
格式,而這其中我們目前使用最多的是TextFile
,SequenceFile
,ORC
和Parquet
。
下面來詳細了解下這2種行列式存儲。
1、ORC
1.1 ORC的存儲結構
我們先從官網上拿到ORC的存儲模型圖
看起來略微有點復雜,那我們稍微簡化一下,我畫了一個簡單的圖來說明一下
左邊的圖就表示了傳統的行式數據庫存儲方式,按行存儲,如果沒有存儲索引的話,如果需要查詢一個字段,就需要把整行的數據都查出來然后做篩選,這么做式比較消耗IO資源的,於是在Hive種最開始也是用了索引的方式來解決這個問題。
但是由於索引的高成本,在「目前的Hive3.X 中,已經廢除了索引」,當然也早就引入了列式存儲。
列式存儲的存儲方式,其實和名字一樣,它是按照一列一列存儲的,如上圖中的右圖,這樣的話如果查詢一個字段的數據,就等於是索引查詢,效率高。但是如果需要查全表,它因為需要分別取所有的列最后匯總,反而更占用資源。於是ORC行列式存儲出現了。
- 在需要全表掃描時,可以按照行組讀取
- 如果需要取列數據,在行組的基礎上,讀取指定的列,而不需要所有行組內所有行的數據和一行內所有字段的數據。
了解了ORC存儲的基本邏輯后,我們再來看看它的存儲模型圖。
同時我也把詳細的文字也附在下面,大家可以對照着看看:
- 條帶( stripe):ORC文件存儲數據的地方,每個stripe一般為HDFS的塊大小。(包含以下3部分)
index data:保存了所在條帶的一些統計信息,以及數據在 stripe中的位置索引信息。 rows data:數據存儲的地方,由多個行組構成,每10000行構成一個行組,數據以流( stream)的形式進行存儲。 stripe footer:保存數據所在的文件目錄
- 文件腳注( file footer):包含了文件中sipe的列表,每個 stripe的行數,以及每個列的數據類型。它還包含每個列的最小值、最大值、行計數、求和等聚合信息。
- postscript:含有壓縮參數和壓縮大小相關的信息
所以其實發現,ORC提供了3級索引,文件級、條帶級、行組級,所以在查詢的時候,利用這些索引可以規避大部分不滿足查詢條件的文件和數據塊。
但注意,ORC中所有數據的描述信息都是和存儲的數據放在一起的,並沒有借助外部的數據庫。
「特別注意:ORC格式的表還支持事務ACID,但是支持事務的表必須為分桶表,所以適用於更新大批量的數據,不建議用事務頻繁的更新小批量的數據」
#開啟並發支持,支持插入、刪除和更新的事務 set hive. support concurrency=truei #支持ACID事務的表必須為分桶表 set hive. enforce bucketing=truei #開啟事物需要開啟動態分區非嚴格模式 set hive.exec,dynamicpartition.mode-nonstrict #設置事務所管理類型為 org. apache.hive.q1. lockage. DbTxnManager #原有的org. apache. hadoop.hive.q1.1 eckmar. DummyTxnManager不支持事務 set hive. txn. manager=org. apache. hadoop. hive. q1. lockmgr DbTxnManageri #開啟在相同的一個 meatore實例運行初始化和清理的線程 set hive. compactor initiator on=true: #設置每個 metastore實例運行的線程數 hadoop set hive. compactor. worker threads=l #(2)創建表 create table student_txn (id int, name string ) #必須支持分桶 clustered by (id) into 2 buckets #在表屬性中添加支持事務 stored as orc TBLPROPERTIES('transactional'='true‘); #(3)插入數據 #插入id為1001,名字為student 1001 insert into table student_txn values('1001','student 1001'); #(4)更新數據 #更新數據 update student_txn set name= 'student 1zh' where id='1001'; # (5)查看表的數據,最終會發現id為1001被改為 sutdent_1zh
1.2關於ORC的Hive配置
表配置屬性(建表時配置,例如tblproperties ('orc.compress'='snappy');
:
- orc.compress:表示ORC文件的壓縮類型,「可選的類型有NONE、ZLB和SNAPPY,默認值是ZLIB(Snappy不支持切片)」---這個配置是最關鍵的。
- orc. compress.Slze:表示壓縮塊( chunk)的大小,默認值是262144(256KB)。
- orc. stripe.size:寫 stripe,可以使用的內存緩沖池大小,默認值是67108864(64MB)
- orc. row. index. stride:行組級別索引的數據量大小,默認是10000,必須要設置成大於等於10000的數
- orc. create index:是否創建行組級別索引,默認是true
- orc. bloom filter. columns:需要創建布隆過濾的組。
- orc. bloom filter fpp:使用布隆過濾器的假正( False Positive)概率,默認值是0. 擴展:在Hive中使用 bloom過濾器,可以用較少的文件空間快速判定數據是否存表中,但是也存在將不屬於這個表的數據判定為屬於這個這表的情況,這個稱之為假正概率,開發者可以調整該概率,但概率越低,布隆過濾器所需要。
2、Parquet
上面說過ORC后,我們對行列式存儲也有了基本的了解,而Parquet是另一種高性能的行列式存儲結構。
2.1 Parquet的存儲結構
既然ORC都那么高效了,那為什么還要再來一個Parquet,那是因為「Parquet是為了使Hadoop生態系統中的任何項目都可以使用壓縮的,高效的列式數據表示形式」
❝ Parquet 是語言無關的,而且不與任何一種數據處理框架綁定在一起,適配多種語言和組件,能夠與 Parquet 配合的組件有:
查詢引擎: Hive, Impala, Pig, Presto, Drill, Tajo, HAWQ, IBM Big SQL
計算框架: MapReduce, Spark, Cascading, Crunch, Scalding, Kite
數據模型: Avro, Thrift, Protocol Buffers, POJOs
❞
再來看看Parquet的存儲結構吧,先看官網給的
嗯,略微有點頭大,我畫個簡易版
Parquet文件是以二進制方式存儲的,所以不可以直接讀取,和ORC一樣,文件的元數據和數據一起存儲,所以Parquet格式文件是自解析的。
- 行組(Row Group):每一個行組包含一定的行數,在一個HDFS文件中至少存儲一個行組,類似於orc的stripe的概念。
- 列塊(Column Chunk):在一個行組中每一列保存在一個列塊中,行組中的所有列連續的存儲在這個行組文件中。一個列塊中的值都是相同類型的,不同的列塊可能使用不同的算法進行壓縮。
- 頁(Page):每一個列塊划分為多個頁,一個頁是最小的編碼的單位,在同一個列塊的不同頁可能使用不同的編碼方式。
2.2Parquet的表配置屬性
- parquet. block size:默認值為134217728byte,即128MB,表示 Row Group在內存中的塊大小。該值設置得大,可以提升 Parquet文件的讀取效率,但是相應在寫的時候需要耗費更多的內存
- parquet. page:size:默認值為1048576byt,即1MB,表示每個頁(page)的大小。這個特指壓縮后的頁大小,在讀取時會先將頁的數據進行解壓。頁是 Parquet操作數據的最小單位,每次讀取時必須讀完一整頁的數據才能訪問數據。這個值如果設置得過小,會導致壓縮時出現性能問題
- parquet. compression:默認值為 UNCOMPRESSED,表示頁的壓縮方式。「可以使用的壓縮方式有 UNCOMPRESSED、 SNAPPY、GZP和LZO」。
- Parquet enable. dictionary:默認為tue,表示是否啟用字典編碼。
- parquet. dictionary page.size:默認值為1048576byte,即1MB。在使用字典編碼時,會在 Parquet的每行每列中創建一個字典頁。使用字典編碼,如果存儲的數據頁中重復的數據較多,能夠起到一個很好的壓縮效果,也能減少每個頁在內存的占用。
3、ORC和Parquet對比
同時,從《Hive性能調優實戰》作者的案例中,2張分別采用ORC和Parquet存儲格式的表,導入同樣的數據,進行sql查詢,「發現使用ORC讀取的行遠小於Parquet」,所以使用ORC作為存儲,可以借助元數據過濾掉更多不需要的數據,查詢時需要的集群資源比Parquet更少。(查看更詳細的性能分析,請移步https://blog.csdn.net/yu616568/article/details/51188479)
「所以ORC在存儲方面看起來還是更勝一籌」
壓縮方式
ble data-draft-node="block" data-draft-type="table" data-size="normal" data-row-style="normal">
存儲和壓縮結合該如何選擇?
根據ORC和parquet的要求,一般就有了
1、ORC格式存儲,Snappy壓縮
create table stu_orc(id int,name string) stored as orc tblproperties ('orc.compress'='snappy');
2、Parquet格式存儲,Lzo壓縮
create table stu_par(id int,name string) stored as parquet tblproperties ('parquet.compression'='lzo');
3、Parquet格式存儲,Snappy壓縮
create table stu_par(id int,name string) stored as parquet tblproperties ('parquet.compression'='snappy');
因為Hive 的SQL會轉化為MR任務,如果該文件是用ORC存儲,Snappy壓縮的,因為Snappy不支持文件分割操作,所以壓縮文件「只會被一個任務所讀取」,如果該壓縮文件很大,那么處理該文件的Map需要花費的時間會遠多於讀取普通文件的Map時間,這就是常說的「Map讀取文件的數據傾斜」。
那么為了避免這種情況的發生,就需要在數據壓縮的時候采用bzip2和Zip等支持文件分割的壓縮算法。但恰恰ORC不支持剛說到的這些壓縮方式,所以這也就成為了大家在可能遇到大文件的情況下不選擇ORC的原因,避免數據傾斜。
在Hve on Spark的方式中,也是一樣的,Spark作為分布式架構,通常會嘗試從多個不同機器上一起讀入數據。要實現這種情況,每個工作節點都必須能夠找到一條新記錄的開端,也就需要該文件可以進行分割,但是有些不可以分割的壓縮格式的文件,必須要單個節點來讀入所有數據,這就很容易產生性能瓶頸。(下一篇文章詳細寫Spark讀取文件的源碼分析)
「所以在實際生產中,使用Parquet存儲,lzo壓縮的方式更為常見,這種情況下可以避免由於讀取不可分割大文件引發的數據傾斜。 但是,如果數據量並不大(預測不會有超大文件,若干G以上)的情況下,使用ORC存儲,snappy壓縮的效率還是非常高的。」