一文讀懂 Hive 底層數據存儲格式

本文講解 Hive 的數據存儲,是 Hive 操作數據的基礎。選擇一個合適的底層數據存儲文件格式,即使在不改變當前 Hive SQL 的情況下,性能也能得到數量級的提升。這種優化方式對學過 MySQL 等關係型數據庫的小夥伴並不陌生,選擇不同的數據存儲引擎,代表着不同的數據組織方式,對於數據庫的表現會有不同的影響。

Hive 數據存儲常用的格式如下:

注:RCFile 和 ORCFile 並不是純粹的列式存儲,它是先基於行對數據表進行分組 (行組),然後對行組進行列式存儲

我們看下這幾種存儲結構的優缺點:

  1. 水平的行存儲結構

行存儲模式就是把一整行存在一起,包含所有的列,這是最常見的模式。這種結構能很好的適應動態的查詢。

比如:select a from tableAselect a, b, c, d, e, f, g from tableA這樣兩個查詢其實查詢的開銷差不多,都需要把所有的行讀進來過一遍,拿出需要的列。

而且這種情況下,屬於同一行的數據都在同一個 HDFS 塊上,重建一行數據的成本比較低。

但是這樣做有兩個主要的弱點:

  1. 垂直的列存儲結構

列存儲是將每列單獨存儲或者將某幾個列作爲列組存在一起。列存儲在執行查詢時可以避免讀取不必要的列。而且一般同列的數據類型一致,取值範圍相對多列混合更小,在這種情況下壓縮數據能達到比較高的壓縮比。

但是這種結構在重建行時比較費勁,尤其當一行的多個列不在一個 HDFS 塊上的時候。比如我們從第一個 DataNode 上拿到 column A,從第二個 DataNode 上拿到了 column B,又從第三個 DataNode 上拿到了 column C,當要把 A,B,C 拼成一行時,就需要把這三個列放到一起重建出行,需要比較大的網絡開銷和運算開銷。

  1. 混合的 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 優點

SequenceFile 的缺點

三、RCFile

RCFile 文件格式是 FaceBook 開源的一種 Hive 的文件存儲格式,首先將表分爲幾個行組,對每個行組內的數據進行按列存儲,每一列的數據都是分開存儲,正是先水平劃分,再垂直劃分的理念。

首先對錶進行行劃分,分成多個行組。一個行組主要包括:

在一般的行存儲中 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 的優化

  1. ORC 擴展了 RCFile 的壓縮,除了 Run-length(遊程編碼),引入了字典編碼和 Bit 編碼。

  2. 每個 task 只輸出單個文件,這樣可以減少 NameNode 的負載;

  3. 支持各種複雜的數據類型,比如:datetime,decimal,以及一些複雜類型 (struct, list, map, 等);

  4. 文件是可切分(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 結構同樣可以分爲三部分:index data、rows data 和 stripe footer:

rows data 存儲兩部分的數據,即 metadata stream 和 data stream:

ORC 在每個文件中提供了 3 個級別的索引:

程序可以藉助 ORC 提供的索引加快數據查找和讀取效率。程序在查詢 ORC 文件類型的表時,會先讀取每一列的索引信息,將查找數據的條件和索引信息進行對比,找到滿足查找條件的文件。

接着根據文件中的索引信息,找到存儲對應的查詢條件數據 stripe,再借助 stripe 的索引信息讀文件中滿足查詢條件的所有 stripe 塊。

之後再根據 stripe 中每個行組的索引信息和查詢條件比對的結果,找到滿足要求的行組。

通過 ORC 這些索引,可以快速定位滿足查詢的數據塊,規避大部分不滿足查詢條件的文件和數據塊,相比於讀取傳統的數據文件,進行查找時需要遍歷全部的數據,使用 ORC 可以避免磁盤和網絡 I/O 的浪費,提升程序的查找效率,提升整個集羣的工作負載。

3. ORC 的數據類型

Hive 在使用 ORC 文件進行存儲數據時,描述這些數據的字段信息、字段 類型信息及編碼等相關信息都是和 ORC 中存儲的數據放在一起的。

ORC 中每個塊中的數據都是自描述的,不依賴外部的數據,也不存儲在 Hive 的元數據庫中。

ORC 提供的數據數據類型包含如下內容:

目前 ORC 基本已經兼容了日常所能用到的絕大部分的字段類型。另外,ORC 中所有的類型都可以接受 NULL 值。

4. ORC 的 ACID 事務的支持

在 Hive 0.14 版本以前,Hive 表的數據只能新增或者整塊刪除分區或表,而不能對錶的單個記錄進行修改。

在 Hive 0.14 版本後,ORC 文件能夠確保 Hive 在工作時的原子性、一致性、隔離性和持久性的 ACID 事務能夠被正確地得到使用,使得對數據更新操作成爲可能

Hive 是面向 OLAP 的,所以它的事務也和 RDMBS 的事務有一定的區別。Hive 的事務被設計成每個事務適用於更新大批量的數據,而不建議用事務頻繁地更新小批量的數據。

創建 Hive 事務表的方法

  1. 設置 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;
  1. 創建表:
create table student_txn
(id int,
 name string
)
clustered by (id) into 2 buckets --必須支持分桶
stored as orc TBLPROPERTIES ('transactional'='true'); --在表屬性中添加支持事務
  1. 插入數據:
--插入id爲1001,名字爲'student_1001
insert into table student_txn values('1001','student_1001');
  1. 更新數據:
update student_txn
set name='student_lzh'
where id='1001';
  1. 查看錶的數據,最終會發現 id 爲 1001 被改爲 sutdent_lzh;
5. ORC 相關的 Hive 配置

表的屬性配置項有如下幾個:

注:在 Hive 中使用布隆 (bloom) 過濾器,可以用較少的文件空間快速判定數據是否存在於表中,但是也存在將不屬於這個表的數據判定爲屬於這個這表的情況,這個情況稱之爲假正概率,可以手動調整該概率,但概率越低,布隆過濾器所需要的空間越多。

五、Parquet

Parquet 是另外的一種高性能行列式的存儲結構,可以適用多種計算框架,被多種查詢引擎所支持,包括 Hive、Impala、Drill 等。

1. Parquet 基本結構:

在一個 Parquet 類型的 Hive 表文件中,數據被分成多個行組,每個列塊又被拆分成若干的頁(Page),如下圖所示:

Parquet 的文件結構

Parquet 在存儲數據時,也同 ORC 一樣記錄這些數據的元數據,這些元數據也同 Parquet 的文件結構一樣,被分成多層文件級別的元數據、列塊級別的元數據及頁級別的元數據。

文件級別的元數據(fileMetadata)記錄主要如下:

列塊的元數據信息如下:

頁頭的元數據信息如下:

程序可以藉助 Parquet 的這些元數據,在讀取數據時過濾掉不需要讀取的大部分文件數據,加快程序的運行速度。

同 ORC 的元數據一樣,Parquet 的這些元數據信息能夠幫助提升程序的運行速度,但是 ORC 在讀取數據時又做了一定的優化,增強了數據的讀取效率。在查詢時所消耗的集羣資源比 Parquet 類型少。

Parquet 在嵌套式結構支持比較完美,而 ORC 多層級嵌套表達起來比較複雜,性能損失較大。

2. 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 壓縮格式對比:

eAiu5p

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