一文讀懂 Hive 底層數據存儲格式
本文講解 Hive 的數據存儲,是 Hive 操作數據的基礎。選擇一個合適的底層數據存儲文件格式,即使在不改變當前 Hive SQL 的情況下,性能也能得到數量級的提升。這種優化方式對學過 MySQL 等關係型數據庫的小夥伴並不陌生,選擇不同的數據存儲引擎,代表着不同的數據組織方式,對於數據庫的表現會有不同的影響。
Hive 數據存儲常用的格式如下:
-
行式存儲:
文本格式(TextFile)
二進制序列化文件 (SequenceFile)
-
列式存儲:
行列式文件(RCFile)
優化的行列式文件(ORCFile)
Apache Parquet
注:RCFile 和 ORCFile 並不是純粹的列式存儲,它是先基於行對數據表進行分組 (行組),然後對行組進行列式存儲
我們看下這幾種存儲結構的優缺點:
- 水平的行存儲結構:
行存儲模式就是把一整行存在一起,包含所有的列,這是最常見的模式。這種結構能很好的適應動態的查詢。
比如:select a from tableA
和 select a, b, c, d, e, f, g from tableA
這樣兩個查詢其實查詢的開銷差不多,都需要把所有的行讀進來過一遍,拿出需要的列。
而且這種情況下,屬於同一行的數據都在同一個 HDFS 塊上,重建一行數據的成本比較低。
但是這樣做有兩個主要的弱點:
-
當一行中有很多列,而我們只需要其中很少的幾列時,我們也不得不把一行中所有的列讀進來,然後從中取出一些列。這樣大大降低了查詢執行的效率。
-
基於多個列做壓縮時,由於不同的列數據類型和取值範圍不同,壓縮比不會太高。
- 垂直的列存儲結構:
列存儲是將每列單獨存儲或者將某幾個列作爲列組存在一起。列存儲在執行查詢時可以避免讀取不必要的列。而且一般同列的數據類型一致,取值範圍相對多列混合更小,在這種情況下壓縮數據能達到比較高的壓縮比。
但是這種結構在重建行時比較費勁,尤其當一行的多個列不在一個 HDFS 塊上的時候。比如我們從第一個 DataNode 上拿到 column A,從第二個 DataNode 上拿到了 column B,又從第三個 DataNode 上拿到了 column C,當要把 A,B,C 拼成一行時,就需要把這三個列放到一起重建出行,需要比較大的網絡開銷和運算開銷。
- 混合的 PAX 存儲結構:
PAX 結構是將行存儲和列存儲混合使用的一種結構,主要是傳統數據庫中提高 CPU 緩存利用率的一種方法,並不能直接用到 HDFS 中。但是 RCFile 和 ORC 是繼承自它的思想,先按行存再按列存。
接下來我們看下在 Hive 中常用的幾種存儲格式:
本文重點講解最後兩種:Apache ORC 和 Apache Parquet,因爲它們以其高效的數據存儲和數據處理性能得以在實際的生產環境中大量運用。
一、TextFile
TextFile 爲 Hive 默認格式,建表時不指定則默認爲這個格式,導入數據時會直接把數據文件拷貝到 hdfs 上不進行處理。
創建一個 TextFile 格式的 Hive 表:
create table if not exists textfile_table
(
ueserid STRING,
movieid STRING,
rating STRING,
ts STRING
)
row formated delimated fields terminated by '\t'
stored as textfile; -- 可不指定(默認格式)
向 TextFile 表中加載數據:
load data local inpath "/root/rating.csv" overwrite into table textfile_table
TextFile 優缺點:
TextFile 格式因爲不對導入的數據文件做處理,所以可以直接使用 load 方式加載數據,其他存儲格式則不能使用 load 直接導入數據文件。所以 TextFile 的加載速度是最高的。
TextFile 格式雖然可以使用 Gzip 壓縮算法,但壓縮後的文件不支持 split。在反序列化過程中,必須逐個字符判斷是不是分隔符和行結束符,因此反序列化開銷會比 SequenceFile 高几十倍。
二、SequenceFile
SequenceFile 是 Hadoop API 提供的一種二進制文件支持,其具有使用方便、可分割、可壓縮的特點。
SequenceFIle 的內部格式取決於是否啓用壓縮,如果是壓縮,則又可以分爲記錄壓縮和塊壓縮。
無壓縮(NONE):如果沒有啓用壓縮 (默認設置) 那麼每個記錄就由它的記錄長度(字節數)、鍵的長度,鍵和值組成。長度字段爲 4 字節。
記錄壓縮(RECORD):記錄壓縮格式與無壓縮格式基本相同,不同的是值字節是用定義在頭部的編碼器來壓縮。注意:鍵是不壓縮的。
塊壓縮(BLOCK):塊壓縮一次壓縮多個記錄,因此它比記錄壓縮更緊湊,而且一般優先選擇。當記錄的字節數達到最小大小,纔會添加到塊。該最小值由 io.seqfile.compress.blocksize 中的屬性定義。默認值是 1000000 字節。格式爲記錄數、鍵長度、鍵、值長度、值。Record 壓縮率低,一般建議使用 BLOCK 壓縮。
創建一個 SequenceFile 格式的 Hive 表:
create table if not exists seqfile_table
(
ueserid STRING,
movieid STRING,
rating STRING,
ts STRING
)
row format delimited
fields terminated by '\t'
stored as sequencefile;
設置壓縮格式爲塊壓縮:
set mapred.output.compression.type=BLOCK;
向 SequenceFile 表中加載數據:
insert overwrite table seqfile_table select * from textfile_table;
SequenceFile 優點:
-
支持基於記錄 (Record) 或塊 (Block) 的數據壓縮。
-
支持 splitable,能夠作爲 MapReduce 的輸入分片。
-
修改簡單:主要負責修改相應的業務邏輯,而不用考慮具體的存儲格式。
SequenceFile 的缺點:
- 需要一個合併文件的過程,且合併後的文件不方便查看。
三、RCFile
RCFile 文件格式是 FaceBook 開源的一種 Hive 的文件存儲格式,首先將表分爲幾個行組,對每個行組內的數據進行按列存儲,每一列的數據都是分開存儲,正是先水平劃分,再垂直劃分的理念。
首先對錶進行行劃分,分成多個行組。一個行組主要包括:
-
16 字節的 HDFS 同步塊信息,主要是爲了區分一個 HDFS 塊上的相鄰行組;
-
元數據的頭部信息主要包括該行組內的存儲的行數、列的字段信息等等;
-
數據部分我們可以看出 RCFile 將每一行,存儲爲一列,將一列存儲爲一行,因爲當表很大,我們的字段很多的時候,我們往往只需要取出固定的一列就可以。
在一般的行存儲中 select a from table
,雖然只是取出一個字段的值,但是還是會遍歷整個表,所以效果和 select * from table
一樣,在 RCFile 中,像前面說的情況,只會讀取該行組的一行。
創建一個 RCFile 的表:
create table if not exists rcfile_table
(
ueserid STRING,
movieid STRING,
rating STRING,
ts STRING
)
row format delimited fields terminated by '\t'
stored as rcfile;
在存儲空間上:
RCFile 是行劃分,列存儲,採用遊程編碼,相同的數據不會重複存儲,很大程度上節約了存儲空間,尤其是字段中包含大量重複數據的時候。
懶加載:
數據存儲到表中都是壓縮的數據,Hive 讀取數據的時候會對其進行解壓縮,但是會針對特定的查詢跳過不需要的列,這樣也就省去了無用的列解壓縮。
如:
select c from table where a>1;
針對行組來說,會對一個行組的 a 列進行解壓縮,如果當前列中有 a>1 的值,然後纔去解壓縮 c。若當前行組中不存在 a>1 的列,那就不用解壓縮 c,從而跳過整個行組。
四、ORCFile
1. ORC 相比較 RCFile 的優點
ORC 是在一定程度上擴展了 RCFile,是對 RCFile 的優化:
-
ORC 擴展了 RCFile 的壓縮,除了 Run-length(遊程編碼),引入了字典編碼和 Bit 編碼。
-
每個 task 只輸出單個文件,這樣可以減少 NameNode 的負載;
-
支持各種複雜的數據類型,比如:datetime,decimal,以及一些複雜類型 (struct, list, map, 等);
-
文件是可切分(Split)的。在 Hive 中使用 ORC 作爲表的文件存儲格式,不僅節省 HDFS 存儲資源,查詢任務的輸入數據量減少,使用的 MapTask 也就減少了。
採用字典編碼,最後存儲的數據便是字典中的值,及每個字典值的長度以及字段在字典中的位置;
採用 Bit 編碼,對所有字段都可採用 Bit 編碼來判斷該列是否爲 null, 如果爲 null 則 Bit 值存爲 0,否則存爲 1,對於爲 null 的字段在實際編碼的時候不需要存儲,也就是說字段若爲 null,是不佔用存儲空間的。
2. ORC 的基本結構
ORCFile 在 RCFile 基礎上引申出來 Stripe 和 Footer 等。每個 ORC 文件首先會被橫向切分成多個 Stripe,而每個 Stripe 內部以列存儲,所有的列存儲在一個文件中,而且每個 stripe 默認的大小是 250MB,相對於 RCFile 默認的行組大小是 4MB,所以比 RCFile 更高效。
下圖是 ORC 的文件結構示意圖:
ORC 文件結構由三部分組成:
-
條帶(stripe):ORC 文件存儲數據的地方。
-
文件腳註(file footer):包含了文件中 stripe 的列表,每個 stripe 的行數,以及每個列的數據類型。它還包含每個列的最小值、最大值、行計數、 求和等聚合信息。
-
postscript:含有壓縮參數和壓縮大小相關的信息。
stripe 結構同樣可以分爲三部分:index data、rows data 和 stripe footer:
-
index data:保存了所在條帶的一些統計信息,以及數據在 stripe 中的位置索引信息。
-
rows data:數據存儲的地方,由多個行組構成,數據以流(stream)的形式進行存儲。
-
stripe footer:保存數據所在的文件目錄。
rows data 存儲兩部分的數據,即 metadata stream 和 data stream:
-
metadata stream:用於描述每個行組的元數據信息。
-
data stream:存儲數據的地方。
ORC 在每個文件中提供了 3 個級別的索引:
-
文件級:這一級的索引信息記錄文件中所有 stripe 的位置信息,以及文件中所存儲的每列數據的統計信息。
-
條帶級別:該級別索引記錄每個 stripe 所存儲數據的統計信息。
-
行組級別:在 stripe 中,每 10000 行構成一個行組,該級別的索引信息 就是記錄這個行組中存儲的數據的統計信息。
程序可以藉助 ORC 提供的索引加快數據查找和讀取效率。程序在查詢 ORC 文件類型的表時,會先讀取每一列的索引信息,將查找數據的條件和索引信息進行對比,找到滿足查找條件的文件。
接着根據文件中的索引信息,找到存儲對應的查詢條件數據 stripe,再借助 stripe 的索引信息讀文件中滿足查詢條件的所有 stripe 塊。
之後再根據 stripe 中每個行組的索引信息和查詢條件比對的結果,找到滿足要求的行組。
通過 ORC 這些索引,可以快速定位滿足查詢的數據塊,規避大部分不滿足查詢條件的文件和數據塊,相比於讀取傳統的數據文件,進行查找時需要遍歷全部的數據,使用 ORC 可以避免磁盤和網絡 I/O 的浪費,提升程序的查找效率,提升整個集羣的工作負載。
3. ORC 的數據類型
Hive 在使用 ORC 文件進行存儲數據時,描述這些數據的字段信息、字段 類型信息及編碼等相關信息都是和 ORC 中存儲的數據放在一起的。
ORC 中每個塊中的數據都是自描述的,不依賴外部的數據,也不存儲在 Hive 的元數據庫中。
ORC 提供的數據數據類型包含如下內容:
-
整型:包含 boolean(1bit)、tinyint(8bit)、smallint(16bit)、int(32bit)、bigint(64bit)。
-
浮點型:包含 float 和 double。
-
字符串類型:包含 string、char 和 varchar。
-
二進制類型:包含 binary。
-
日期和時間類型:包含 timestamp 和 date。·
-
複雜類型:包含 struct、list、map 和 union 類型。
目前 ORC 基本已經兼容了日常所能用到的絕大部分的字段類型。另外,ORC 中所有的類型都可以接受 NULL 值。
4. ORC 的 ACID 事務的支持
在 Hive 0.14 版本以前,Hive 表的數據只能新增或者整塊刪除分區或表,而不能對錶的單個記錄進行修改。
在 Hive 0.14 版本後,ORC 文件能夠確保 Hive 在工作時的原子性、一致性、隔離性和持久性的 ACID 事務能夠被正確地得到使用,使得對數據更新操作成爲可能。
Hive 是面向 OLAP 的,所以它的事務也和 RDMBS 的事務有一定的區別。Hive 的事務被設計成每個事務適用於更新大批量的數據,而不建議用事務頻繁地更新小批量的數據。
創建 Hive 事務表的方法:
- 設置 hive 環境參數:
--開啓併發支持,支持插入、刪除和更新的事務
set hive.support.concurrency=true;
--支持ACID事務的表必須爲分桶表
set hive.enforce.bucketing=true;
--開啓事物需要開啓動態分區非嚴格模式
set hive.exec.dynamic.partition.mode=nonstrict;
--設置事務所管理類型爲org.apache.hive.ql.lockmgr.DbTxnManager
--原有的org.apache.hadoop.hive.ql.lockmgr.DummyTxnManager不支持事務
set hive.txn.manager=org.apache.hadoop.hive.ql.lockmgr.DbTxnManager;
--開啓在相同的一個meatore實例運行初始化和清理的線程
set hive.compactor.initiator.on=true;
--設置每個metastore實例運行的線程數
set hive.compactor.worker.threads=1;
- 創建表:
create table student_txn
(id int,
name string
)
clustered by (id) into 2 buckets --必須支持分桶
stored as orc TBLPROPERTIES ('transactional'='true'); --在表屬性中添加支持事務
- 插入數據:
--插入id爲1001,名字爲'student_1001
insert into table student_txn values('1001','student_1001');
- 更新數據:
update student_txn
set name='student_lzh'
where id='1001';
- 查看錶的數據,最終會發現 id 爲 1001 被改爲 sutdent_lzh;
5. ORC 相關的 Hive 配置
表的屬性配置項有如下幾個:
-
orc.compress
:表示 ORC 文件的壓縮類型,可選的類型有 NONE、ZLIB 和 SNAPPY,默認值是 ZLIB。 -
orc.compress.size
:表示壓縮塊(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.05。
注:在 Hive 中使用布隆 (bloom) 過濾器,可以用較少的文件空間快速判定數據是否存在於表中,但是也存在將不屬於這個表的數據判定爲屬於這個這表的情況,這個情況稱之爲假正概率,可以手動調整該概率,但概率越低,布隆過濾器所需要的空間越多。
五、Parquet
Parquet 是另外的一種高性能行列式的存儲結構,可以適用多種計算框架,被多種查詢引擎所支持,包括 Hive、Impala、Drill 等。
1. Parquet 基本結構:
在一個 Parquet 類型的 Hive 表文件中,數據被分成多個行組,每個列塊又被拆分成若干的頁(Page),如下圖所示:
Parquet 在存儲數據時,也同 ORC 一樣記錄這些數據的元數據,這些元數據也同 Parquet 的文件結構一樣,被分成多層文件級別的元數據、列塊級別的元數據及頁級別的元數據。
文件級別的元數據(fileMetadata)記錄主要如下:
-
表結構信息(Schema);
-
該文件的記錄數;
-
該文件擁有的行組,以及每個行組的數據總量,記錄數;
-
每個行組下,列塊的文件偏移量。
列塊的元數據信息如下:
-
記錄該列塊的未壓縮和壓縮後的數據大小和壓縮編碼;
-
數據頁的偏移量;
-
索引頁的偏移量;
-
列塊的數據記錄數。
頁頭的元數據信息如下:
-
該頁的編碼信息;
-
該頁的數據記錄數。
程序可以藉助 Parquet 的這些元數據,在讀取數據時過濾掉不需要讀取的大部分文件數據,加快程序的運行速度。
同 ORC 的元數據一樣,Parquet 的這些元數據信息能夠幫助提升程序的運行速度,但是 ORC 在讀取數據時又做了一定的優化,增強了數據的讀取效率。在查詢時所消耗的集羣資源比 Parquet 類型少。
Parquet 在嵌套式結構支持比較完美,而 ORC 多層級嵌套表達起來比較複雜,性能損失較大。
2. Parquet 的相關配置:
可以根據不同場景需求進行適當的參數調整,實現程序優化。
-
parquet.block.size
:默認值爲 134217728byte,即 128MB,表示 RowGroup 在內存中的塊大小。該值設置得大,可以提升 Parquet 文件的讀取效率,但是相應在寫的時候需要耗費更多的內存。 -
parquet.page.size
:默認值爲 1048576byte,即 1MB,表示每個頁 (page)的大小。這個特指壓縮後的頁大小,在讀取時會先將頁的數據進行解壓。頁是 Parquet 操作數據的最小單位,每次讀取時必須讀完一整頁的數據才能訪問數據。這個值如果設置得過小,會導致壓縮時出現性能問題。 -
parquet.compression
:默認值爲 UNCOMPRESSED(不壓縮),表示頁的壓縮式。可以使用的壓縮方式有 UNCOMPRESSED、SNAPPY、GZIP 和 LZO。 -
parquet.enable.dictionary
:默認爲 true,表示是否啓用字典編碼。 -
parquet.dictionary.page.size
:默認值爲 1048576byte,即 1MB。在使用字典編碼時,會在 Parquet 的每行每列中創建一個字典頁。使用字典編碼,如果存儲的數據頁中重複的數據較多,能夠起到一個很好的壓縮效果,也能減少每個頁在內存的佔用。
3. 使用 Spark 引擎時 Parquet 表的壓縮格式配置:
Spark 天然支持 Parquet,併爲其推薦的存儲格式 (默認存儲爲 parquet)。
對於 Parquet 表的壓縮格式分以下兩種情況進行配置:
對於分區表:
需要通過 Parquet 本身的配置項 parquet.compression
設置 Parquet 表的數據壓縮格式。如在建表語句中設置:"parquet.compression"="snappy"
。
對於非分區表:
需要通過 spark.sql.parquet.compression.code
配置項來設置 Parquet 類型的數據壓縮格式。直接設置parquet.compression
配置項是無效的,因爲它會讀取 spark.sql.parquet.compression.codec
配置項的值。
當 spark.sql.parquet.compression.codec
未做設置時默認值爲 snappy,parquet.compression
會讀取該默認值。
因此,spark.sql.parquet.compression.codec
配置項只適用於設置非分區表的 Parquet 壓縮格式。
4. Parquet 和 ORC 壓縮格式對比:
ORC 表支持 None、Zlib、Snappy 壓縮,默認爲 ZLIB 壓縮。但這 3 種壓縮格式不支持切分,所以適合單個文件不是特別大的場景。使用 Zlib 壓縮率高,但效率差一些;使用 Snappy 效率高,但壓縮率低。
Parquet 表支持 Uncompress、Snappy、Gzip、Lzo 壓縮,默認不壓縮 (Uncompressed)。其中 Lzo 壓縮是支持切分的,所以在表的單個文件較大的場景會選擇 Lzo 格式。Gzip 方式壓縮率高,效率低;而 Snappy、Lzo 效率高,壓縮率低。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/WTHewY1m_WkAk0IWUf9Qyw