Hive 底層資料儲存格式詳解

語言: CN / TW / HK

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

Hive 資料儲存常用的格式如下:

  • 行式儲存:

    文字格式(TextFile)

    二進位制序列化檔案 (SequenceFile)

  • 列式儲存:

    行列式檔案(RCFile)

    優化的行列式檔案(ORCFile)

    Apache Parquet

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

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

  1. 水平的行儲存結構:

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

比如: select a from tableA 和  select 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 delimitedfields 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 的優化:

  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):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 事務表的方法:

  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_1001insert into table student_txn values('1001','student_1001');

複製程式碼

  1. 更新資料:

update student_txnset name='student_lzh'where id='1001';

複製程式碼

  1. 查看錶的資料,最終會發現 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 的檔案結構

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 壓縮格式。