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

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

兩者的區(qū)別:
數(shù)據(jù)倉(cāng)庫(kù)是一個(gè)優(yōu)化的數(shù)據(jù)庫(kù),用于分析來自事務(wù)系統(tǒng)和業(yè)務(wù)線應(yīng)用程序的關(guān)系數(shù)據(jù)。
數(shù)據(jù)湖存儲(chǔ)來自業(yè)務(wù)線應(yīng)用程序的關(guān)系數(shù)據(jù),以及來自移動(dòng)應(yīng)用程序、IoT 設(shè)備和社交媒體的非關(guān)系數(shù)據(jù)。
數(shù)據(jù)湖并不能替代數(shù)據(jù)倉(cāng)庫(kù),數(shù)據(jù)倉(cāng)庫(kù)在高效的報(bào)表和可視化分析中仍有優(yōu)勢(shì)。
2 Hudi 基礎(chǔ)功能
2.1 Hudi 簡(jiǎn)介
Apache Hudi 由 Uber 開發(fā)并開源,該項(xiàng)目在 2016 年開始開發(fā),并于 2017 年開源,2019年 1 月進(jìn)入 Apache 孵化器,且 2020 年 6 月稱為 Apache 頂級(jí)項(xiàng)目,目前最新版本:0.10.1 版本。
Hudi 一開始支持 Spark 進(jìn)行數(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ù)集存儲(chǔ)。
簡(jiǎn)單來說,Hudi 是一種針對(duì)分析型業(yè)務(wù)的、掃描優(yōu)化的數(shù)據(jù)存儲(chǔ)抽象,它能夠使 DFS 數(shù)據(jù)集在分鐘級(jí)的時(shí)延內(nèi)支持變更,也支持下游系統(tǒng)對(duì)這個(gè)數(shù)據(jù)集的增量處理。
2.2 Hudi 功能
Hudi 是在大數(shù)據(jù)存儲(chǔ)上的一個(gè)數(shù)據(jù)集,可以將 Change Logs 通過 upsert 的方式合并進(jìn) Hudi;
Hudi 對(duì)上可以暴露成一個(gè)普通 Hive 或 Spark 表,通過 API 或命令行可以獲取到增量修改的信息,繼續(xù)供下游消費(fèi);
Hudi 保管修改歷史,可以做時(shí)間旅行或回退;
Hudi 內(nèi)部有主鍵到文件級(jí)的索引,默認(rèn)是記錄到文件的布隆過濾器;

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

2.4 Hudi 的 架構(gòu)

通過 DeltaStreammer、Flink、Spark 等工具,將數(shù)據(jù)攝取到數(shù)據(jù)湖存儲(chǔ),可使用HDFS 作為數(shù)據(jù)湖的數(shù)據(jù)存儲(chǔ);
基于 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 湖倉(cāng)一體架構(gòu)
Hudi 對(duì)于 Flink 友好支持以后,可以使用 Flink + Hudi 構(gòu)建實(shí)時(shí)湖倉(cāng)一體架構(gòu),數(shù)據(jù)的時(shí)效性可以到分鐘級(jí),能很好的滿足業(yè)務(wù)準(zhǔn)實(shí)時(shí)數(shù)倉(cāng)的需求。
通過湖倉(cāng)一體、流批一體,準(zhǔn)實(shí)時(shí)場(chǎng)景下做到了:數(shù)據(jù)同源、同計(jì)算引擎、同存儲(chǔ)、同計(jì)算口徑。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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