HDFS 廉頗老矣?基於對象存儲的數據湖構建新思路

作者 | 王夏、滕昱、孫偉

編輯 | 蔡芳芳

1 什麼是數據湖?爲什麼是數據湖?

我們經常會被問到一個企業大數據架構的問題:隨着企業收集 / 產生的數據越來越多,如何設計一套高效廉價的大數據架構,在儘可能多保留所有原始數據內容的同時還可以支持 “無縫接入” 的新的分析算法。本文所要介紹的數據湖解決方案可能是解決這個難題的一種新思路。

數據湖,實質上是一種數字資產的組織形式。這種新的組織形式希望向數據工程師提供一個靈活的平臺,用以對更加廣泛類型的數據,實施更加靈活的解決方案。

如上圖所示,這是一個典型的數據湖架構,自底至上分爲三層:存儲層、數據定義層和計算層。相較於傳統的數據倉庫解決方案,數據湖最重要的特點是使用了更加靈活的數據定義,爲計算層提供了一個統一的數據訪問抽象。

但是,對多種數據格式的支持,使數據湖失去了訪問結構化數據的便利性,下面我們可以對比一下傳統數據倉庫的實現。

數據倉庫是非常傳統的面向結構化數據的解決方案。在數據倉庫中,數據被拆分並存儲在不同的關係型數據庫、非關係型數據庫中,絕大多數的查詢優化被內建在了數據庫解決方案中,用戶需要熟悉數據庫解決方案,或者依賴數據庫管理員,對相應的查詢進行優化。對於一些規模較大的查詢操作,數據倉庫則需要依賴特定的計算平臺,通過讀取統一管理的元信息,跨越不同的數據庫進行檢索。

而在本文將會介紹的數據湖方案中,結構化數據並不再依賴(或減少依賴)特定的數據倉庫方案。並且伴隨着 Apache Iceberg、Apache Hudi、Delta 這些面向結構化數據的數據定義框架的誕生和發展,計算層和存儲層的解決方案也變得靈活多樣。通過對計算層和 / 或存儲層進行一定的抽象,這些框架不再依賴特定的一種方案,並允許用戶使用統一的業務邏輯在不同的解決方案間切換,以獲得最佳的開發運行效率。

作爲長期企業級對象存儲解決方案的提供者,我們在對這些開源方案進行對比研究後,選擇了 Apache Iceberg 作爲我們的數據湖方案組成部分。其絕佳的靈活性使得我們的對象存儲產品可以對其進行完美的適配。下面,我們會詳細介紹 Apache Iceberg 的工作和存儲細節,並詳細闡述對象存儲和 HDFS 在這個場景下的優劣。

2Apache Iceberg 如何管理其存儲?

在研究了多個在數據湖內管理結構化數據的解決方案後,我們發現,無論是 Iceberg,還是其他方案,在存儲上都有着類似的架構,如下圖所示:

在存儲上,這些解決方案往往包括三個部分。最左側是管理數據,用來提供一個類似數據庫或命名空間的管理能力。在管理數據右邊的是表元數據,用來管理表格的元數據,例如表名、版本等。最右側的則是表數據,用來存儲結構化數據本身。

在 Iceberg 中,表數據由這幾部分構成:

  1. 版本信息,指的是當前表格的版本,包括版本所對應的:

2. Manifest List 文件,引用多個 Manifest 文件。每一個文件都是表格的全量數據快照。

3. Manifest 文件,引用多個 Data 文件,是一個比較簡單的集合概念。

4. Data 文件,存儲表格的原始數據文件,數據支持使用 Apache Parquet、Apache ORC 和 Apache Avro 格式進行存儲。

對於特定版本的數據快照,即 Manifest List 文件,Iceberg 使用了 2 層一對多的結構來引用所有的 Data 文件。這種 2 層的設計便於 Iceberg 進行批量數據文件的操作,例如提交大量 Data 文件時,合併其成爲單獨的 Manifest,這樣設計的 Manifest List 只需要添加 1 行即可完成表數據的修改。由於 Iceberg 中的表數據文件不會進行修改,對文件的變更越小,越能降低對存儲的額外消耗。

同時由於表數據文件不會進行修改,在數據寫入完成後,讀取數據將不會出現讀取到部分數據的情況。即當用戶讀取到一個特定的快照時,其 Manifest List 引用的數據已經被確定,在讀取過程中,就會正確的處理全量數據,或者因爲其他意外導致操作失敗。這有效的避免了靜默的成功產生讀取了部分數據或者錯誤數據的情況,爲用戶獲取正確的數據提供了有效的保障。

Iceberg 的表元數據則非常簡單,僅僅包括當前表的名稱和版本信息。所有的 Schema 和 Properties 都由 Iceberg 本身管理。

爲了管理這些數據,Iceberg 使用了 Catalog 作爲其抽象,具有如下圖所示的能力:

Catalog 的能力主要包括三部分:

  1. 文件 IO,用來讀、寫、刪除所有表數據文件。

  2. 表操作,用於完成:

3 對象存儲的優勢和挑戰

在 Iceberg 的代碼倉庫中,包含了以下幾種 Catalog 的實現:

絕大多數 Catalog 使用 Aapche HDFS 作爲數據文件的存儲,因爲 HDFS 是當前被廣泛使用的開源存儲組件。它擁有一個文件系統抽象,從而讓開發者更加便利的使用其作爲存儲引擎。但是在近 1-2 年以來,隨着混合雲 / 多雲和邊緣端的高速發展,對象存儲在很多方面呈現出取代 HDFS 的趨勢。

下文將會詳細敘述相較於 HDFS,對象存儲在數據湖場景下所體現的優勢和所面臨的挑戰,以及解決方案。

對象存儲易於集羣擴展

HDFS 使用了 Name Node 作爲元數據管理服務,所有文件的元數據交由 Name Node 進行管理。因此,Name Node 實際上成爲了 HDFS 系統中的單點服務,無法進行橫向擴展。在逐步增長的數據規模下,這樣的架構會慢慢的體現出劣勢,往往體現在需要使用越來越高的配置運行 Name Node,需要對 Name Node 設計高可用方案等。

在對象存儲中,集羣的橫向擴展往往比較容易,因爲對象存儲通常使用了一致性哈希對元數據進行分區處理。對於已經被分區處理的元數據,可以在多個服務間移動,在數據量不斷增長的情況下,通過擴展集羣可以有效地提高服務容量,在部分服務出現異常時,對應分區的數據可以快速切換到可用服務,用以重新提供服務。當數據規模增長到分區的限制時,對象存儲可以重新設定分區,以支持更高規模的元數據。

總之,被拆分的元數據爲集羣擴展性提供了基礎支持,使得對象存儲能夠根據其配置,進行合理的規劃,消化新的軟硬件資源,從而提供高容量的服務。

對象存儲在支持海量小文件上的架構優勢

如前所述,HDFS 的元數據受限於 Name Node 的架構。當出現海量小文件時 (例如單節點 100 億 -150 億量級 4KB 文件),因爲其元數據的佔比較高,HDFS 的 Name Node 空間消耗極大,導致 Data Node 在尚空的情況下,Name Node 已經無法有效的提供服務。社區使用了多種手段優化小文件存儲,但其基本原理都是將小文件合併成大文件,這或多或少的對性能和交互便利性產生了一定的影響。

如前節所示,對象存儲由於使用了分佈式的元數據管理設計,本質上元數據和數據都是用一致的底層存儲方式,可以無縫的 scale out 到整個底層硬件層上,解決了元數據容量擴充的挑戰。加之近年來,對象存儲在快速發展中通常利用多種介質對元數據進行加速或緩存,使得其可以利用存儲領域的新技術,例如全面引入 NVMe-oF,對元數據查詢進行優化。

所以在對象存儲中,元數據不再受限於單個節點的物理資源,對於小文件這種元數據與數據接近的數據湖場景,對象存儲更能夠平衡元數據和數據的資源配比,有效的利用整個系統的物理資源對小文件進行索引,使得單一的節點也能夠容納海量小文件,而不受限於某些特定節點的資源限制。

對象存儲天然支持多站點部署

對於存儲的數據,如果需要異地備份,或者多機房備份,就需要進行多站點部署。而在很多企業應用中 (例如金融客戶),這又是個必選項。

HDFS 本身並不支持多站點部署。有一些商業軟件試圖提供多站點支持,但基本都是基於一個額外的消息系統進行異步數據複製。那麼使用這樣 “混合” 架構的代價是需要一定量額外工作的去支持兩個系統,更不談設計一個 “僅一次性” 消息系統本身又是一個挑戰。

而絕大部分對象存儲則天然支持多站點部署。通常用戶只要配置數據的複製規則,對象存儲就會建立起互聯的通道,將增量和 / 或存量數據進行同步。對於配置了規則的數據,你可以在其中任何一個站點進行訪問,由於跨站點的數據具備最終一致性,在有限可預期的時間內,用戶會獲取到最新的數據。

綜上所述,一個支持多站點數據訪問的單一存儲抽象,是現階段 HDFS 所不能提供的。

對象存儲低存儲開銷(Lower TCO)

任何分佈式存儲的在設計上都需要一些額外的副本數據來抵禦硬件故障產生的數據丟失風險。

在 HDFS 中,默認使用 3 副本存儲數據,數據存儲了 3 份,對於其中任意 2 份數據,如果因爲軟硬件故障發生了損壞,可以使用剩餘的 1 份,保障了數據的準確性。但是,因此對於一次寫入 - 多次讀取的場景,其存儲開銷較大。對於存儲有限的用戶,往往希望在保障數據準確性的前提下,降低多副本帶來的存儲空間浪費。

因此,EC(糾刪碼)成爲了存儲領域的一種解決方案。在對象存儲中,數據天然支持 EC,如圖所示,數據使用了 10+2 的 EC 編碼,數據將會被均勻的拆分成 10 份,並根據這 10 份數據計算出 2 份糾刪數據。這 12 份數據中,任意 10 份就可以恢復出完整數據,存儲開銷僅僅是 1.2 倍。在這種模式下,數據密度被提升,有限的存儲可以發揮出更高的價值。本質上,使用 EC 是在計算能力(計算 EC 編碼)和存儲空間中做的 trade-off。但是,考慮到當代 x86 架構上很多微碼上的優化,使用 EC 的方案在經濟性上的比拼已經遠遠勝出。

近年來,HDFS 版本也支持了 EC,但是受限於其存儲模型,經過 EC 的數據不再支持 append 等存儲操作。並且由於是對文件整體進行 EC 編碼,當文件較小時,EC 算法可能無法拆分出足夠的數據塊,導致 EC 過後反而出現空間變大的情況,而在對象存儲中,小文件的數據可以合併至塊(Chunk)進行 EC,進一步加大對象存儲在小文件上優勢。

對象存儲如何解決追加上傳(append)的場景

在 S3 的標準 API 中,上傳數據需要預先知道對象的大小,因此在追加上傳的場景下,其調用方法無法像 HDFS 那樣簡潔。所以在具體實現中,追加寫的操作需要在本地預先處理,並以整體上傳。

而具體對於對象存儲而言,上傳有 2 種模式:小的對象,使用 Put Object,大的對象,使用 Multipart upload。

在對象寫入過程中,如果當前對象的大小始終沒有達到 Multipart upload 的要求時,直接使用 Put Object 上傳對象。當對象在寫入過程中,大小達到了 Multipart upload 的要求時,會立刻創建一個 Multipart upload 流程,將當前已有的數據提交爲第 1 個 part,並將後續的數據寫入新的緩存,逐一上傳。在對象寫入完成後,統一將這次 Multipart upload 的所有 part 信息提交,服務器將會將其拼裝成一個完整的對象。這意味着,追加上傳需要依賴一定的本地物理資源(內存 / 本地存儲)進行數據的緩存。

儘管如此有着這樣的限制,但是它也提供了額外的優勢:

  1. 對象存儲額外提供了不可變性。對於一個已經上傳完成的數據,沒有任何操作可以改變其部分內容,只能寫入一個新的對象覆蓋原有對象,這意味着不會出現部分上傳的對象,導致服務讀取到部分數據。

  2. 對於非常大的對象,使用 Multipart upload 可以異步進行上傳,在上傳分段的過程中,寫入的流程不需要被阻塞,只需要在最終完成上傳時,確保所有的分段都上傳成功即可。這樣可以提高數據的吞吐,有效提高寫入文件的效率。

對象存儲應對原子 Rename 的挑戰

爲了闡明這個問題,我們需要先看看 Iceberg 在修改表格時發生了什麼:

這是一個使用 Flink 向 Iceberg 表格中插入數據的基本流程,主要分爲兩個部分:數據文件的寫入和表格元信息的提交。

在寫入數據文件時, Flink Data workers 將從數據源中逐行讀取數據,根據當前定義的 Schema,解析行中的數據,計算分區信息,將該行寫入對應分區的數據文件中。當到達檢查點時,Data Workers 將會切換寫入新的數據文件,同時完成正在寫入的數據文件,所有完成的文件會被打包成文件清單。文件清單包含了數據文件的路徑和其統計信息,這些信息將被移交至 Commit Worker 對錶格的元數據進行變更。在變更時,需要讀取當前的表格版本,如圖所示,當前讀取到的版本號爲 006。緊接着將所有的數據文件信息與版本號爲 006 的表格進行合併,生成新的表格元數據,並將版本號更新爲 007。注意,這一過程是線性一致性的,因此,對於多個提交者:

Commit Worker 1 讀取到了 006,處理了數據變更,並提交了新的版本 007。在這個過程中,如果發起了另一個更新請求,使得另一個 Commit Worker 2 也讀取到了 006,那麼在其最終提交版本時,如果提交了 007,就會產生衝突。此時,必須有機制保障它不提交 007,並在感知到失敗後,重試去提交 008,避免當前變更的丟失。這就是 Iceberg 所要求的線性一致性。

在官方內置的 Catalog 實現中,主要存在兩個流派的設計。一個是基於 Apache Hadoop 的實現,使用了原子的重命名確保特定的版本被唯一提交:

在 HDFS 中,由於重命名操作是原子的,HDFS Catalog 使用了每個版本唯一的文件作爲標記,如圖則是 007。在提交的時候,會先將提交後的元信息寫入一個隨機文件名稱的文件,並嘗試將這個文件重命名成指定的 007,如果重命名失敗,則會返回重新提交。這樣,意味着在提交的時候,HDFS 能夠完美的保證線性一致性。但是,在查詢表格最新的版本時,則會出現一些性能問題:HDFS Catalog 會列出當前所有的版本文件,並選擇最大的一個作爲最新的版本。

在對象存儲上,則是沿用了第三方鎖的實現:

當最終表格提交的時候,使用一個鎖用來確保其他人無法提交新的版本,此時檢查自己希望提交的版本是否存在,如果版本不存在,則直接提交,如果版本不存在,則獲取最新的版本再次提交。

商業對象存儲的解決方法

當然,針對標準 S3 API 在前 2 小節提到的限制,現有商用對象存儲(公有云 / 混合雲)比較常見的做法是擴充 S3 的實現,提供 Append 和 CAS 語義來解決。Dell EMC ECS 對象存儲,也使用了類似的思路實現更高效的 Iceberg catalog。

ECS 支持使用 Append 操作,如果用戶本地緩存受限時,使用 Append 的操作可以完美應對順序寫入未知長度文件的場景。注意在這個場景下,如果在寫入過程中出現了客戶端程序崩潰等現象,部分數據可能會殘留在 ECS 中留待後臺清理流程去處理。但是 ECS 仍舊提供了用戶可見對象數據的完整性。

在併發提交的場景下,ECS 支持使用 If-Match 和 If-None-Match 對對象進行 CAS 操作。對於對象的讀寫,ECS 是強一致性的,通過提交前一個版本的 ETag 信息(常見實現是文件的 MD5),ECS 會在最終更新對象信息時進行檢查,如果出現 ETag 不匹配則會拒絕對象的更新,從而保障數據以合適的順序被提交。

如圖,ECS 使用了一個 table.object 存儲對象的版本信息,每次提交新的版本時,攜帶原版本的 E-Tag 更新,因此 v007_b 會在最終提交時失敗,並獲取最新版本重新提交。相較於 HDFS 的原子性 rename,CAS 提供了更高的靈活度,只需要一個版本對象記錄版本信息即可。由於 Listing 操作在對象存儲上有着比較大的開銷,使用單一的版本對象能夠極大的提升獲取最新版本的性能。

基於以上兩個方面,使用 ECS,就可以滿足 Iceberg 的所有需求,而不依賴其他第三方應用提供諸如元數據和鎖的管理。使用 Apache Iceberg 和 ECS,可以構建出一套完整的、針對結構化數據的數據湖解決方案。

4 總結

在對 Apache Iceberg 進入深入探索後,我們作爲對象存儲產品的提供方,對數據湖的解決方案有了一些思考。社區在推動數據湖的解決方案時,對存儲層的良好定義使得更加多的存儲產品可以在大數據解決方案中扮演全新的角色。

作爲數據湖解決方案中的新星,Apache Iceberg 定義了良好的表格格式,用於幫助用戶組織數據。其靈活的特性,使得無論是 Apache Flink、Apache Spark 等計算層解決方案,還是 HDFS、對象存儲等存儲層的解決方案,都能夠與其完美適配。

在未來,我們會積極的參與到社區中,參與到數據湖、大數據解決方案的建設中,我們正在積極準備向社區貢獻 ECS catalog 的代碼,希望在未來,愈加高性能、高自由度的對象存儲,能夠爲數據分析領域添磚加瓦,能夠在當下的數據熱潮中,成爲更多人的選擇,讓存儲變得簡單,讓數據發揮更大的價值。


本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/L0eoxt1q0zHfDmVxRUSj1Q