最近數(shù)據(jù)湖非常火,今天跟大家嘮嘮數(shù)據(jù)湖三劍客之Hudi。有人很奇怪,為啥叫胡迪?跟玩具總動員有啥關(guān)系?其實Hudi跟玩具總動員一點關(guān)系都沒有。因為Hudi的全稱叫做“Hadoop Upserts Deletes and Incrementals(原為 Hadoop Upserts anD Incrementals)”,就是基于Hadoop體系的,支持Upserts、Deletes 和 Incremental 數(shù)據(jù)處理。今天就從以下幾方面全面闡述 Hudi 組件核心知識點。
1.數(shù)據(jù)湖與數(shù)據(jù)倉庫的區(qū)別?2.Hudi 基礎功能3 Hudi 數(shù)據(jù)管理4 Hudi 核心點解析
1 數(shù)據(jù)湖與數(shù)據(jù)倉庫的區(qū)別?
數(shù)據(jù)倉庫
數(shù)據(jù)倉庫(英語:Data Warehouse,簡稱數(shù)倉、DW),是一個用于存儲、分析、報告的數(shù)據(jù)系統(tǒng)。
數(shù)據(jù)倉庫的目的是構(gòu)建面向分析的集成化數(shù)據(jù)環(huán)境,分析結(jié)果為企業(yè)提供決策支持(Decision Support)。

數(shù)據(jù)湖
數(shù)據(jù)湖(Data Lake)和數(shù)據(jù)庫、數(shù)據(jù)倉庫一樣,都是數(shù)據(jù)存儲的設計模式,現(xiàn)在企業(yè)的數(shù)據(jù)倉庫都會通過分層的方式將數(shù)據(jù)存儲在文件夾、文件中。
數(shù)據(jù)湖是一個集中式數(shù)據(jù)存儲庫,用來存儲大量的原始數(shù)據(jù),使用平面架構(gòu)來存儲數(shù)據(jù)。
定義:一個以原始格式(通常是對象塊或文件)存儲數(shù)據(jù)的系統(tǒng)或存儲庫,通常是所有企業(yè)數(shù)據(jù)的單一存儲。
數(shù)據(jù)湖可以包括來自關(guān)系數(shù)據(jù)庫的結(jié)構(gòu)化數(shù)據(jù)(行和列)、半結(jié)構(gòu)化數(shù)據(jù)(CSV、日志、XML、JSON)、非結(jié)構(gòu)化數(shù)據(jù)(電子郵件、文檔、pdf)和二進制數(shù)據(jù)(圖像、音頻、視頻)。
數(shù)據(jù)湖中數(shù)據(jù),用于報告、可視化、高級分析和機器學習等任務。

兩者的區(qū)別:
數(shù)據(jù)倉庫是一個優(yōu)化的數(shù)據(jù)庫,用于分析來自事務系統(tǒng)和業(yè)務線應用程序的關(guān)系數(shù)據(jù)。
數(shù)據(jù)湖存儲來自業(yè)務線應用程序的關(guān)系數(shù)據(jù),以及來自移動應用程序、IoT 設備和社交媒體的非關(guān)系數(shù)據(jù)。
數(shù)據(jù)湖并不能替代數(shù)據(jù)倉庫,數(shù)據(jù)倉庫在高效的報表和可視化分析中仍有優(yōu)勢。
2 Hudi 基礎功能
2.1 Hudi 簡介
Apache Hudi 由 Uber 開發(fā)并開源,該項目在 2016 年開始開發(fā),并于 2017 年開源,2019年 1 月進入 Apache 孵化器,且 2020 年 6 月稱為 Apache 頂級項目,目前最新版本:0.10.1 版本。
Hudi 一開始支持 Spark 進行數(shù)據(jù)攝入(批量 Batch 和流式 Streaming),從 0.7.0 版本開始,逐漸與 Flink 整合,主要在于 Flink SQL 整合,還支持 Flink SQL CDC。

Hudi(Hadoop Upserts anD Incrementals縮寫)是目前市面上流行的三大開源數(shù)據(jù)湖方案之一。
用于管理分布式文件系統(tǒng) DFS 上大型分析數(shù)據(jù)集存儲。
簡單來說,Hudi 是一種針對分析型業(yè)務的、掃描優(yōu)化的數(shù)據(jù)存儲抽象,它能夠使 DFS 數(shù)據(jù)集在分鐘級的時延內(nèi)支持變更,也支持下游系統(tǒng)對這個數(shù)據(jù)集的增量處理。
2.2 Hudi 功能
Hudi 是在大數(shù)據(jù)存儲上的一個數(shù)據(jù)集,可以將 Change Logs 通過 upsert 的方式合并進 Hudi;
Hudi 對上可以暴露成一個普通 Hive 或 Spark 表,通過 API 或命令行可以獲取到增量修改的信息,繼續(xù)供下游消費;
Hudi 保管修改歷史,可以做時間旅行或回退;
Hudi 內(nèi)部有主鍵到文件級的索引,默認是記錄到文件的布隆過濾器;

2.3 Hudi 的特性
Apache Hudi 使得用戶能在 Hadoop 兼容的存儲之上存儲大量數(shù)據(jù),同時它還提供兩種原語,不僅可以批處理,還可以在數(shù)據(jù)湖上進行流處理。
Update/Delete 記錄:Hudi 使用細粒度的文件/記錄級別索引來支持 Update/Delete 記錄,同時還提供寫操作的事務保證。查詢會處理最后一個提交的快照,并基于此輸出結(jié)果。
變更流:Hudi 對獲取數(shù)據(jù)變更提供了一流的支持:可以從給定的 時間點 獲取給定表中已 updated / inserted / deleted 的所有記錄的增量流,并解鎖新的查詢姿勢(類別)。
Apache Hudi 本身不存儲數(shù)據(jù),僅僅管理數(shù)據(jù)。
Apache Hudi 也不分析數(shù)據(jù),需要使用計算分析引擎,查詢和保存數(shù)據(jù),比如 Spark 或 Flink;
使用 Hudi 時,加載 jar 包,底層調(diào)用 API,所以需要依據(jù)使用大數(shù)據(jù)框架版本,編譯 Hudi 源碼,獲取對應依賴jar包。

2.4 Hudi 的 架構(gòu)

通過 DeltaStreammer、Flink、Spark 等工具,將數(shù)據(jù)攝取到數(shù)據(jù)湖存儲,可使用HDFS 作為數(shù)據(jù)湖的數(shù)據(jù)存儲;
基于 HDFS 可以構(gòu)建 Hudi 的數(shù)據(jù)湖;
Hudi 提供統(tǒng)一的訪問 Spark 數(shù)據(jù)源和 Flink 數(shù)據(jù)源;
外部通過不同引擎,如:Spark、Flink、Presto、Hive、Impala、Aliyun DLA、AWS Redshit 訪問接口;

2.5 湖倉一體架構(gòu)
Hudi 對于 Flink 友好支持以后,可以使用 Flink + Hudi 構(gòu)建實時湖倉一體架構(gòu),數(shù)據(jù)的時效性可以到分鐘級,能很好的滿足業(yè)務準實時數(shù)倉的需求。
通過湖倉一體、流批一體,準實時場景下做到了:數(shù)據(jù)同源、同計算引擎、同存儲、同計算口徑。

3 Hudi 數(shù)據(jù)管理
3.1 Hudi 表數(shù)據(jù)結(jié)構(gòu)
Hudi 表的數(shù)據(jù)文件,可以使用操作系統(tǒng)的文件系統(tǒng)存儲,也可以使用 HDFS 這種分布式的文件系統(tǒng)存儲。為了后續(xù)分析性能和數(shù)據(jù)的可靠性,一般使用 HDFS 進行存儲。以 HDFS 存儲來看,一個 Hudi 表的存儲文件分為兩類。

.hoodie 文件:由于 CRUD 的零散性,每一次的操作都會生成一個文件,這些小文件越來越多后,會嚴重影響 HDFS 的性能,Hudi 設計了一套文件合并機制。.hoodie 文件夾中存放了對應的 **文件合并操作 **相關(guān)的日志文件。
amricas 和 asia 相關(guān)的路徑是 實際的數(shù)據(jù)文件,按分區(qū)存儲,分區(qū)的路徑 key 是可以指定的。
3.1.1 ?.hoodie 文件
Hudi 把隨著時間流逝,對表的一系列 CRUD 操作叫做 Timeline,Timeline 中某一次的操作,叫做 Instant。
Hudi 的核心是維護 **Timeline **在不同時間對表執(zhí)行的所有操作,instant 這有助于提供表的即時視圖,同時還有效地支持按到達順序檢索數(shù)據(jù)。Hudi Instant 由以下組件組成:
Instant Action: 記錄本次操作是一次操作類型 數(shù)據(jù)提交(COMMITS),還是文件合并(COMPACTION),或者是文件清理(CLEANS);
Instant Time,本次操作發(fā)生的時間,通常是時間戳(例如:20190117010349),它按照動作開始時間的順序單調(diào)遞增。
lState,操作的狀態(tài),發(fā)起(REQUESTED),進行中(INFLIGHT),還是已完成(COMPLETED);
.hoodie 文件夾中存放對應操作的狀態(tài)記錄:

3.1.2 數(shù)據(jù)文件
Hudi 真實的數(shù)據(jù)文件使用 Parquet 文件格式存儲

其中包含一個 metadata 元數(shù)據(jù)文件和數(shù)據(jù)文件 parquet 列式存儲。
Hudi 為了實現(xiàn)數(shù)據(jù)的 CRUD,需要能夠唯一標識一條記錄,Hudi 將把數(shù)據(jù)集中的 唯一字段(record key ) + 數(shù)據(jù)所在分區(qū) (partitionPath) 聯(lián)合起來當做 數(shù)據(jù)的唯一鍵。
3.2 數(shù)據(jù)存儲概述
Hudi 數(shù)據(jù)集的 組織目錄結(jié)構(gòu) 與 Hive 表示非常相似,一份數(shù)據(jù)集對應這一個根目錄。數(shù)據(jù)集被 打散為多個分區(qū),分區(qū)字段以文件夾形式存在,該文件夾包含該分區(qū)的所有文件。

在根目錄下,每個分區(qū)都有唯一的分區(qū)路徑,每個分區(qū)數(shù)據(jù)存儲在多個文件中。

每個文件都有唯一的 fileId 和生成文件的 commit 標識。如果發(fā)生更新操作時,多個文件共享相同的 fileId,但會有不同的 commit。
3.3 Metadata 元數(shù)據(jù)
以時間軸(Timeline)的形式將數(shù)據(jù)集上的各項操作元數(shù)據(jù)維護起來,以支持數(shù)據(jù)集的瞬態(tài)視圖,這部分元數(shù)據(jù)存儲于根目錄下的元數(shù)據(jù)目錄。一共有三種類型的元數(shù)據(jù):
Commits:一個單獨的commit包含對數(shù)據(jù)集之上一批數(shù)據(jù)的一次原子寫入操作的相關(guān)信息。我們用單調(diào)遞增的時間戳來標識commits,標定的是一次寫入操作的開始。
Cleans:用于清除數(shù)據(jù)集中不再被查詢所用到的舊版本文件的后臺活動。
Compactions:用于協(xié)調(diào)Hudi內(nèi)部的數(shù)據(jù)結(jié)構(gòu)差異的后臺活動。例如,將更新操作由基于行存的日志文件歸集到列存數(shù)據(jù)上

3.4 Index 索引
Hudi 維護著一個索引,以支持在記錄 key 存在情況下,將新記錄的 key 快速映射到對應的fileId。
Bloom filter:存儲于數(shù)據(jù)文件頁腳。默認選項,不依賴外部系統(tǒng)實現(xiàn)。數(shù)據(jù)和索引始終保持一致。
Apache HBase :可高效查找一小批 key。在索引標記期間,此選項可能快幾秒鐘。

3.4.1 索引策略
工作負載 1:對事實表
許多公司將大量事務數(shù)據(jù)存儲在 NoSQL 數(shù)據(jù)存儲中。例如,拼車情況下的行程表、股票買賣、電子商務網(wǎng)站中的訂單。這些表通常會隨著對最新數(shù)據(jù)的隨機更新而不斷增長,而長尾更新會針對較舊的數(shù)據(jù),這可能是由于交易在以后結(jié)算/數(shù)據(jù)更正所致。換句話說,大多數(shù)更新進入最新的分區(qū),很少有更新進入較舊的分區(qū)。

圖1:事實表的典型更新模式
對于這樣的工作負載,BLOOM 索引表現(xiàn)良好,因為索引查找 將基于大小合適的布隆過濾器修剪大量數(shù)據(jù)文件。此外,如果可以構(gòu)造鍵以使它們具有一定的順序,則要比較的文件數(shù)量會通過范圍修剪進一步減少。
Hudi 使用所有文件鍵范圍構(gòu)建一個區(qū)間樹,并有效地過濾掉更新/刪除記錄中與任何鍵范圍不匹配的文件。
為了有效地將傳入的記錄鍵與布隆過濾器進行比較,即最小數(shù)量的布隆過濾器讀取和跨執(zhí)行程序的統(tǒng)一工作分配,Hudi 利用輸入記錄的緩存并采用可以使用統(tǒng)計信息消除數(shù)據(jù)偏差的自定義分區(qū)器。有時,如果布隆過濾器誤報率很高,它可能會增加混洗的數(shù)據(jù)量以執(zhí)行查找。
Hudi 支持動態(tài)布隆過濾器(使用啟用 hoodie.bloom.index.filter.type=DYNAMIC_V0),它根據(jù)存儲在給定文件中的記錄數(shù)調(diào)整其大小,以提供配置的誤報率。
工作負載 2:對事件表
事件流無處不在。來自 Apache Kafka 或類似消息總線的事件通常是事實表大小的 10-100 倍,并且通常將 時間(事件的到達時間/處理時間)視為一等公民。
例如,**物聯(lián)網(wǎng)事件流、點擊流數(shù)據(jù)、廣告印象 **等。插入和更新僅跨越最后幾個分區(qū),因為這些大多是僅附加數(shù)據(jù)。鑒于可以在端到端管道中的任何位置引入重復事件,因此在存儲到數(shù)據(jù)湖之前進行重復數(shù)據(jù)刪除是一項常見要求。

一般來說,這是一個非常具有挑戰(zhàn)性的問題,需要以較低的成本解決。雖然,我們甚至可以使用鍵值存儲來使用 HBASE 索引執(zhí)行重復數(shù)據(jù)刪除,但索引存儲成本會隨著事件的數(shù)量線性增長,因此可能會非常昂貴。
實際上,BLOOM 帶有范圍修剪的索引是這里的最佳解決方案。人們可以利用時間通常是一等公民這一事實并構(gòu)造一個鍵,event_ts + event_id 例如插入的記錄具有單調(diào)遞增的鍵。即使在最新的表分區(qū)中,也可以通過修剪大量文件來產(chǎn)生巨大的回報。
工作負載 3:隨機更新/刪除維度表
這些類型的表格通常包含高維數(shù)據(jù)并保存參考數(shù)據(jù),例如 用戶資料、商家信息。這些是高保真表,其中更新通常很小,但也分布在許多分區(qū)和數(shù)據(jù)文件中,數(shù)據(jù)集從舊到新。通常,這些表也是未分區(qū)的,因為也沒有對這些表進行分區(qū)的好方法。
如前所述,BLOOM 如果無法通過比較范圍/過濾器來刪除大量文件,則索引可能不會產(chǎn)生好處。在這樣的隨機寫入工作負載中,更新最終會觸及表中的大多數(shù)文件,因此布隆過濾器通常會根據(jù)一些傳入的更新指示所有文件的真陽性。因此,我們最終會比較范圍/過濾器,只是為了最終檢查所有文件的傳入更新。
SIMPLE 索引將更適合,因為它不進行任何基于預先修剪的操作,而是直接與每個數(shù)據(jù)文件中感興趣的字段連接 。HBASE 如果操作開銷是可接受的,并且可以為這些表提供更好的查找時間,則可以使用索引。
在使用全局索引時,用戶還應該考慮設置 hoodie.bloom.index.update.partition.path=true或hoodie.simple.index.update.partition.path=true 處理分區(qū)路徑值可能因更新而改變的情況,例如用戶表按家鄉(xiāng)分區(qū);用戶搬遷到不同的城市。這些表也是 Merge-On-Read 表類型的絕佳候選者。
3.5 ?Data 數(shù)據(jù)
Hudi 以兩種不同的存儲格式存儲所有攝取的數(shù)據(jù),用戶可選擇滿足下列條件的任意數(shù)據(jù)格式:
讀優(yōu)化的列存格式(ROFormat):缺省值為 Apache Parquet;
寫優(yōu)化的行存格式(WOFormat):缺省值為 Apache Avro;

4 ?Hudi 核心點解析
4.1 基本概念
Hudi 提供了Hudi 表的概念,這些表支持 CRUD 操作,可以利用現(xiàn)有的大數(shù)據(jù)集群比如 HDFS 做數(shù)據(jù)文件存儲,然后使用 SparkSQL 或 Hive 等分析引擎進行數(shù)據(jù)分析查詢。

Hudi 表的三個主要組件:
1) 有序的時間軸元數(shù)據(jù),類似于數(shù)據(jù)庫事務日志。
2) 分層布局的數(shù)據(jù)文件:實際寫入表中的數(shù)據(jù);
3)索引(多種實現(xiàn)方式):映射包含指定記錄的數(shù)據(jù)集。
4.1.1 時間軸Timeline
Hudi 核心:
在所有的表中維護了一個包含在不同的即時(Instant)時間對數(shù)據(jù)集操作(比如新增、修改或刪除)的時間軸(Timeline)。
在每一次對 Hudi 表的數(shù)據(jù)集操作 時都會在該表的 Timeline 上生成一個 Instant,從而可以實現(xiàn)在僅查詢某個時間點之后成功提交的數(shù)據(jù),或是僅查詢某個時間點之前的數(shù)據(jù),有效避免了掃描更大時間范圍的數(shù)據(jù)。
可以高效地只查詢更改前的文件(如在某個Instant提交了更改操作后,僅query某個時間點之前的數(shù)據(jù),則仍可以query修改前的數(shù)據(jù))。

Timeline 是 Hudi 用來管理提交(commit)的抽象,每個 commit 都綁定一個固定時間戳,分散到時間線上。
在 Timeline 上,每個 commit 被抽象為一個 HoodieInstant,一個 instant 記錄了一次提交 (commit) 的行為、時間戳、和狀態(tài)。

圖中采用時間(小時)作為分區(qū)字段,從 10:00 開始陸續(xù)產(chǎn)生各種 commits,10:20 來了一條 9:00 的數(shù)據(jù),該數(shù)據(jù)仍然可以落到 9:00 對應的分區(qū),通過 timeline 直接消費 10:00 之后的增量更新(只消費有新 commits 的 group),那么這條延遲的數(shù)據(jù)仍然可以被消費到。
時間軸(Timeline)的實現(xiàn)類(位于hudi-common-xx.jar中),時間軸相關(guān)的實現(xiàn)類位于 org.apache.hudi.common.table.timeline 包下.

4.1.2 文件管理
Hudi 將 DFS 上的數(shù)據(jù)集組織到基本路徑(HoodieWriteConfig.BASEPATHPROP)下的目錄結(jié)構(gòu)中。
數(shù)據(jù)集分為多個分區(qū)(DataSourceOptions.PARTITIONPATHFIELDOPT_KEY),這些分區(qū)與Hive表非常相似,是包含該分區(qū)的數(shù)據(jù)文件的文件夾。

在每個分區(qū)內(nèi),文件被組織為文件組,由文件 id 充當唯一標識。每個文件組包含多個文件切片,其中每個切片包含在某個即時時間的提交/壓縮生成的基本列文件(.parquet)以及一組日志文件(.log),該文件包含自生成基本文件以來對基本文件的插入/更新。

Hudi 的 base file (parquet 文件) 在 footer 的 meta 去記錄了 record key 組成的 BloomFilter,用于在 file based index 的實現(xiàn)中實現(xiàn)高效率的 key contains 檢測。
Hudi 的 log (avro 文件)是自己編碼的,通過積攢數(shù)據(jù) buffer 以 LogBlock 為單位寫出,每個 LogBlock 包含 magic number、size、content、footer 等信息,用于數(shù)據(jù)讀、校驗和過濾。

4.1.3 索引 Index
Hudi通過
索引機制提供
高效的Upsert操作,該機制會將一個
RecordKey+PartitionPath組合的方式作為唯一標識映射到一個文件ID,而且這個唯一標識和文件組/文件ID之間的映射自記錄被寫入文件組開始就不會再改變。
- 全局索引:在全表的所有分區(qū)范圍下強制要求鍵保持唯一,即確保對給定的鍵有且只有一個對應的記錄。
- 非全局索引:僅在表的某一個分區(qū)內(nèi)強制要求鍵保持唯一,它依靠寫入器為同一個記錄的更刪提供一致的分區(qū)路徑。

4.2 表的存儲類型
4.2.1 數(shù)據(jù)計算模型
Hudi 是 Uber 主導開發(fā)的開源數(shù)據(jù)湖框架,所以大部分的出發(fā)點都來源于 Uber 自身場景,比如司機數(shù)據(jù)和乘客數(shù)據(jù)通過訂單 Id 來做 Join 等。
在 Hudi 過去的使用場景里,和大部分公司的架構(gòu)類似,采用批式和流式共存的 Lambda 架構(gòu),后來Uber提出增量Incremental模型,相對批式來講,更加實時;相對流式而言,更加經(jīng)濟。

4.2.1.1批式模型(Batch)
批式模型就是使用 MapReduce、Hive、Spark 等典型的批計算引擎,以小時任務或者天任務的形式來做數(shù)據(jù)計算。
延遲:小時級延遲或者天級別延遲。這里的延遲不單單指的是定時任務的時間,在數(shù)據(jù)架構(gòu)里,這里的延遲時間通常是定時任務間隔時間 + 一系列依賴任務的計算時間 + 數(shù)據(jù)平臺最終可以展示結(jié)果的時間。數(shù)據(jù)量大、邏輯復雜的情況下,小時任務計算的數(shù)據(jù)通常真正延遲的時間是 2-3 小時。
數(shù)據(jù)完整度:數(shù)據(jù)較完整。以處理時間為例,小時級別的任務,通常計算的原始數(shù)據(jù)已經(jīng)包含了小時內(nèi)的所有數(shù)據(jù),所以得到的數(shù)據(jù)相對較完整。但如果業(yè)務需求是事件時間,這里涉及到終端的一些延遲上報機制,在這里,批式計算任務就很難派上用場。
成本:成本很低。只有在做任務計算時,才會占用資源,如果不做任務計算,可以將這部分批式計算資源出讓給在線業(yè)務使用。從另一個角度來說成本是挺高的,如原始數(shù)據(jù)做了一些增刪改查,數(shù)據(jù)晚到的情況,那么批式任務是要全量重新計算。

4.2.1.2流式模型(Stream)
流式模型,典型的就是使用 Flink 來進行實時的數(shù)據(jù)計算。
延遲:很短,甚至是實時。
數(shù)據(jù)完整度:較差。因為流式引擎不會等到所有數(shù)據(jù)到齊之后再開始計算,所以有一個 watermark 的概念,當數(shù)據(jù)的時間小于 watermark 時,就會被丟棄,這樣是無法對數(shù)據(jù)完整度有一個絕對的保障。在互聯(lián)網(wǎng)場景中,流式模型主要用于活動時的數(shù)據(jù)大盤展示,對數(shù)據(jù)的完整度要求并不算很高。在大部分場景中,用戶需要開發(fā)兩個程序,一是流式數(shù)據(jù)生產(chǎn)流式結(jié)果,二是批式計算任務,用于次日修復實時結(jié)果。
成本:很高。因為流式任務是常駐的,并且對于多流 Join 的場景,通常要借助內(nèi)存或者數(shù)據(jù)庫來做 state 的存儲,不管是序列化開銷,還是和外部組件交互產(chǎn)生的額外 IO,在大數(shù)據(jù)量下都是不容忽視的。

4.2.1.3 增量模型(Incremental)
針對批式和流式的優(yōu)缺點,Uber 提出了增量模型(Incremental Mode),相對批式來講,更加實時;相對流式而言,更加經(jīng)濟。
增量模型,簡單來講,是以 mini batch 的形式來跑準實時任務。Hudi 在增量模型中支持了兩個最重要的特性:
Upsert:這個主要是解決批式模型中,數(shù)據(jù)不能插入、更新的問題,有了這個特性,可以往 Hive 中寫入增量數(shù)據(jù),而不是每次進行完全的覆蓋。(Hudi 自身維護了 key->file 的映射,所以當 upsert 時很容易找到 key 對應的文件)
Incremental Query:增量查詢,減少計算的原始數(shù)據(jù)量。以 Uber 中司機和乘客的數(shù)據(jù)流 Join 為例,每次抓取兩條數(shù)據(jù)流中的增量數(shù)據(jù)進行批式的 Join 即可,相比流式數(shù)據(jù)而言,成本要降低幾個數(shù)量級。

4.2.2 查詢類型(Query Type)
Hudi支持三種不同的查詢表的方式:Snapshot Queries、Incremental Queries和Read Optimized Queries。

4.2.2.1 快照查詢(Snapshot Queries)
類型一:Snapshot Queries(快照查詢)
查詢某個增量提交操作中數(shù)據(jù)集的最新快照,先進行動態(tài)合并最新的基本文件(Parquet)和增量文件(Avro)來提供近實時數(shù)據(jù)集(通常會存在幾分鐘的延遲)。
讀取所有 partiiton 下每個 FileGroup 最新的 FileSlice 中的文件,Copy On Write 表讀 parquet 文件,Merge On Read 表讀 parquet + log 文件

4.2.2.2 增量查詢(Incremental Queries)
類型二:Incremental Queries(增量查詢)
僅查詢新寫入數(shù)據(jù)集的文件,需要指定一個Commit/Compaction的即時時間(位于Timeline上的某個Instant)作為條件,來查詢此條件之后的新數(shù)據(jù)。
可查看自給定commit/delta commit即時操作以來新寫入的數(shù)據(jù),有效的提供變更流來啟用增量數(shù)據(jù)管道。

4.2.2.3 讀優(yōu)化查詢(Read Optimized Queries)
類型三:Read Optimized Queries(讀優(yōu)化查詢)
直接查詢基本文件(數(shù)據(jù)集的最新快照),其實就是列式文件(Parquet)。并保證與非Hudi列式數(shù)據(jù)集相比,具有相同的列式查詢性能。
可查看給定的commit/compact即時操作的表的最新快照。
讀優(yōu)化查詢和快照查詢相同僅訪問基本文件,提供給定文件片自上次執(zhí)行壓縮操作以來的數(shù)據(jù)。通常查詢數(shù)據(jù)的最新程度的保證取決于壓縮策略

4.2.3 Hudi 支持表類型
Hudi提供兩類型表:寫時復制(Copy on Write,COW)表和讀時合并(Merge On Read,MOR)表。
對于 Copy-On-Write Table,用戶的 update 會重寫數(shù)據(jù)所在的文件,所以是一個寫放大很高,但是讀放大為 0,適合寫少讀多的場景。
對于 Merge-On-Read Table,整體的結(jié)構(gòu)有點像 LSM-Tree,用戶的寫入先寫入到 delta data 中,這部分數(shù)據(jù)使用行存,這部分 delta data 可以手動 merge 到存量文件中,整理為 parquet 的列存結(jié)構(gòu)。

4.2.3.1 ?寫時復制表(COW)
Copy on Write 簡稱 COW,顧名思義,它是在數(shù)據(jù)寫入的時候,復制一份原來的拷貝,在其基礎上添加新數(shù)據(jù)。
正在讀數(shù)據(jù)的請求,讀取的是最近的完整副本,這類似Mysql 的MVCC的思想。

優(yōu)點:讀取時,只讀取對應分區(qū)的一個數(shù)據(jù)文件即可,較為高效;
缺點:數(shù)據(jù)寫入的時候,需要復制一個先前的副本再在其基礎上生成新的數(shù)據(jù)文件,這個過程比較耗時

COW表主要使用列式文件格式(Parquet)存儲數(shù)據(jù),在寫入數(shù)據(jù)過程中,執(zhí)行同步合并,更新數(shù)據(jù)版本并重寫數(shù)據(jù)文件,類似RDBMS中的B-Tree更新。
更新update:在更新記錄時,Hudi會先找到包含更新數(shù)據(jù)的文件,然后再使用更新值(最新的數(shù)據(jù))重寫該文件,包含其他記錄的文件保持不變。當突然有大量寫操作時會導致重寫大量文件,從而導致極大的I/O開銷。
讀取read:在讀取數(shù)據(jù)時,通過讀取最新的數(shù)據(jù)文件來獲取最新的更新,此存儲類型適用于少量寫入和大量讀取的場景
4.2.3.2 讀時合并表(MOR)
Merge On Read 簡稱MOR,新插入的數(shù)據(jù)存儲在delta log 中,定期再將delta log合并進行parquet數(shù)據(jù)文件。
讀取數(shù)據(jù)時,會將delta log跟老的數(shù)據(jù)文件做merge,得到完整的數(shù)據(jù)返回。下圖演示了MOR的兩種數(shù)據(jù)讀寫方式

優(yōu)點:由于寫入數(shù)據(jù)先寫delta log,且delta log較小,所以寫入成本較低;
缺點:需要定期合并整理compact,否則碎片文件較多。讀取性能較差,因為需要將delta log和老數(shù)據(jù)文件合并;
MOR 表是 COW 表的升級版,它使用列式(parquet)與行式(avro)文件混合的方式存儲數(shù)據(jù)。在更新記錄時,類似NoSQL中的LSM-Tree更新。
更新:在更新記錄時,僅更新到增量文件(Avro)中,然后進行異步(或同步)的compaction,最后創(chuàng)建列式文件(parquet)的新版本。此存儲類型適合頻繁寫的工作負載,因為新記錄是以追加的模式寫入增量文件中。
讀取:在讀取數(shù)據(jù)集時,需要先將增量文件與舊文件進行合并,然后生成列式文件成功后,再進行查詢。
4.2.3.3 COW VS MOR
對于寫時復制(COW)和讀時合并(MOR)writer來說,Hudi的WriteClient是相同的。
COW 表,用戶在 snapshot 讀取的時候會掃描所有最新的 FileSlice 下的 base file。
MOR 表,在 READ OPTIMIZED 模式下,只會讀最近的經(jīng)過 compaction 的 commit。

4.2.4 數(shù)據(jù)寫操作類型
在 Hudi 數(shù)據(jù)湖框架中支持三種方式寫入數(shù)據(jù):UPSERT(插入更新)、INSERT(插入)和BULK INSERT(寫排序)。
UPSERT:默認行為,數(shù)據(jù)先通過 index 打標(INSERT/UPDATE),有一些啟發(fā)式算法決定消息的組織以優(yōu)化文件的大小
INSERT:跳過 index,寫入效率更高
BULK**_INSERT**:寫排序,對大數(shù)據(jù)量的 Hudi 表初始化友好,對文件大小的限制 best effort(寫 HFile)

4.2.4.1 寫流程(upsert)
(1)Copy On Write類型表,UPSERT 寫入流程
第一步、先對 records 按照 record key 去重;
第二步、首先對這批數(shù)據(jù)創(chuàng)建索引 (HoodieKey => HoodieRecordLocation);通過索引區(qū)分哪些 records 是 update,哪些 records 是 insert(key 第一次寫入);
第三步、對于 update 消息,會直接找到對應 key 所在的最新 FileSlice 的 base 文件,并做 merge 后寫新的 base file (新的 FileSlice);
第四步、對于 insert 消息,會掃描當前 partition 的所有 SmallFile(小于一定大小的 base file),然后 merge 寫新的 FileSlice;如果沒有 SmallFile,直接寫新的 FileGroup + FileSlice;
(2)Merge On Read類型表,UPSERT 寫入流程
第一步、先對 records 按照 record key 去重(可選)
第二步、首先對這批數(shù)據(jù)創(chuàng)建索引 (HoodieKey => HoodieRecordLocation);通過索引區(qū)分哪些 records 是 update,哪些 records 是 insert(key 第一次寫入)
第三步、如果是 insert 消息,如果 log file 不可建索引(默認),會嘗試 merge 分區(qū)內(nèi)最小的 base file (不包含 log file 的 FileSlice),生成新的 FileSlice;如果沒有 base file 就新寫一個 FileGroup + FileSlice + base file;如果 log file 可建索引,嘗試 append 小的 log file,如果沒有就新寫一個 FileGroup + FileSlice + base file
第四步、如果是 update 消息,寫對應的 file group + file slice,直接 append 最新的 log file(如果碰巧是當前最小的小文件,會 merge base file,生成新的 file slice)log file 大小達到閾值會 roll over 一個新的
4.2.4.2 寫流程(Insert)
(1) Copy On Write類型表,INSERT 寫入流程
第一步、先對 records 按照 record key 去重(可選);
第二步、不會創(chuàng)建 Index;
第三步、如果有小的 base file 文件,merge base file,生成新的 FileSlice + base file,否則直接寫新的 FileSlice + base file;
(2) Merge On Read類型表,INSERT 寫入流程
第一步、先對 records 按照 record key 去重(可選);
第二步、不會創(chuàng)建 Index;
第三步、如果 log file 可索引,并且有小的 FileSlice,嘗試追加或?qū)懽钚碌?log file;如果 log file 不可索引,寫一個新的 FileSlice + base file。
(部分內(nèi)容來源網(wǎng)絡,如有侵權(quán)請聯(lián)系刪除)