JuiceFS 在數據湖存儲架構上的探索
- 2022 年 5 月 5 日
- 筆記
大家好,我是來自 Juicedata 的高昌健,今天想跟大家分享的主題是《JuiceFS 在數據湖存儲架構上的探索》,以下是今天分享的提綱:
首先我會簡單的介紹一下大數據存儲架構變遷以及它們的優缺點,然後介紹什麼是 JuiceFS,其次的話會再重點介紹一下關於 JuiceFS 和數據湖的一些結合和關聯,最後會介紹一下 JuiceFS 和數據湖生態的集成。
大數據存儲架構變遷
縱觀整個大數據存儲架構的變遷,可以看到有非常明顯的三個階段:第一個階段就是從最早的 Hadoop、Hive 等項目誕生之後,有了數據倉庫(Data Warehouse)的概念。隨著數倉的逐步發展,同時有了雲的誕生,對象存儲的誕生,以及大數據與 AI 的時代到來之後,數據湖(Data Lake)這個概念就被凸顯了出來。最近兩三年有一個新的概念,或者是說到了一個新的階段叫做湖倉一體(Lakehouse)。傳統數倉大家都比較了解,今天會著重看一下後面這兩個階段,也就是數據湖和湖倉一體。
為什麼要有「數據湖」?
數據湖很重要的一個誕生契機,其實是解決數據孤島(Data Silos)問題。產生數據孤島的根本原因,來自於不同的業務或者不同團隊,因為一些歷史原因造成了數據之間其實是一個孤島或者互相之間沒有辦法去做連接。
隨著不同業務的引入,在企業內部數據的格式會變得越來越多樣,除了最早的傳統的結構化數據以外,會發現還有很多半結構化的甚至是非結構化的數據。這些半結構化和非結構化數據也希望能逐步引入到整個公司的數據管理或者運維裡面來,傳統數倉的架構或者說存儲的模型此時就沒有辦法去滿足這種多樣性的數據格式的存儲需求。
然後第三點是分散的數據管理,這點其實是跟第一點數據孤島也是有關聯的。因為你的數據是分布或者分散在很多不同的地方的,數據的管理或者一些許可權的控制上,也會相對的分散。這個時候你如果要去針對不同的業務與不同的團隊去做管理,也會是一個比較大的工作量。
第四點是存儲與計算的耦合(簡稱「存算耦合」),也是跟傳統 Hadoop 的架構有關,傳統的像 HDFS、YARN 的架構,是針對存算耦合架構來設計的,但在對於現在基於公有雲的大數據架構來說,這種存算耦合的架構就比較缺乏彈性了,不管是在運維的彈性上,還是對成本的控制上。
最後一點隨著 AI 行業的發展,在機器學習或深度學習這塊的數據加入進來之後,也是希望能夠在數倉或者說整個大數據架構裡面為基於機器學習或深度學習的業務提供更好的支援。不僅是存儲數據,例如還需要對接深度學習的框架,所以就要提供一些介面的支援,比如 POSIX 等對演算法工程師更友好的方式,而不是傳統的通過 SQL 或一些其它的方式來提供給業務團隊。
什麼是「數據湖」?
這裡引用維基百科上的一句簡介:
A data lake is a system or repository of data stored in its natural/raw format, usually object blobs or files.
其中一個比較重要的定義是 natural/raw format(原始格式),跟傳統數倉比較大的區別是我們會傾向於把數據以原始的格式先存到數據湖裡面來。數倉其實也還是存在的但它是一個後置的過程,為了實現這樣一個數據湖,最根本的是需要一個足夠便宜且能夠支援海量數據規模的底層存儲。目前看下來在雲上的話,對象存儲是一個非常好的選擇,它既做到了便宜可靠,同時也能夠支援海量的數據。但對象存儲也不是一個絕對的方案,後面會詳細地去做一些比較。
簡單來說就是「 Everything in one place」,意思是所有數據都先放到數據湖裡面來,你要做數倉也好,做一些其他的後置 ETL 也好,那是下一個階段的事情,但前提是要把所有的數據都放在一起。「後置 ETL」的意思是說 ETL 依然存在也依然需要,只是它變成了一個後置的流程。因為用到了對象存儲,以及存算分離的架構,所以在整個的架構設計上也會更加的雲原生 。
為什麼要有「湖倉一體」?
在整個數據湖的架構裡面數倉依然是存在的 ,但是它在整個 pipeline 的階段被後置了,必然就會帶來一些數據的滯後。同時傳統的像 Hive 這些組件,其實你要做到近實時或者基於 Hive 來做增量的數據更新是比較麻煩的,特別是如果你要把分區(partition)的時間窗口縮得很短的話。
之前提到的機器學習和深度學習的結合問題,在數據湖階段也還是存在。雖然有了數據湖,但對於整個深度學習這塊的支援也還是不太夠,所以在湖倉一體這個階段依然是需要解決的一個問題。
然後就是數據重複拷貝和重複 ETL,因為 ETL 是後置的,數倉也是後置的,所以有很多數據有可能是會從湖裡面再同步或複製到數倉裡面,就會帶來一些數據的重複拷貝或者重複 ETL,重複兩次甚至三次都有可能。
最後就是基於對象存儲這樣的存儲類型,希望能夠提供更多高級特性的支援,比如 ACID 事務、多版本數據、索引、零拷貝克隆等。
什麼是「湖倉一體」?
湖倉一體有一些關鍵的因素,其中第一個是需要一個統一開放的底層文件格式,這個格式比如說可以是 Parquet、ORC 等業界公認的格式。第二點我們需要一個開放的存儲層,具體來講是類似 Delta Lake、Iceberg、Hudi 的一些開源組件。第三點是要有開放的計算引擎集成,不管你使用哪一種存儲,都需要能夠支援上面多種多樣的計算引擎,而不是把用戶或者業務團隊限定在某一個引擎裡面,不管用 Spark 也好,Presto 也好,用其它的商業引擎也好,可以做到多樣化的支援。最後一點就是和深度學習框架的結合,這裡拿 Uber 開源的 Petastorm 項目舉例,Petastorm 是為 TensorFlow、PyTorch 等框架提供 Parquet 格式讀寫支援的組件,目前初步做到了一些對深度學習框架的支援。
JuiceFS 簡介
JuiceFS 一個開源的雲原生分散式文件系統,為雲環境設計,提供完備的 POSIX、HDFS 和 S3 API 兼容性。使用 JuiceFS 存儲數據,數據本身會被持久化在對象存儲(例如 Amazon S3),相對應的元數據可以按需持久化在 Redis、MySQL、TiKV 等多種資料庫中。
目前在 GitHub 上已經有超過 5000 個 star,也有超過 50 個外部貢獻者來一起參與這個項目的維護。
JuiceFS 從架構設計上來說,更傾向於開放結合的態度。眾所周知文件系統裡面最重要的一個組件就是元數據引擎,JuiceFS 希望能夠結合已有的開源項目,比如說 Redis、SQL 資料庫、分散式 KV 等,把它們納入進來作為整個 JuiceFS 架構裡面的一個組件。在數據存儲方面,目前 JuiceFS 也已經支援超過 30 種底層的存儲系統,除了最主要的對象存儲,還支援像 Ceph、MinIO、Ozone 這樣開源的組件。同時 JuiceFS 也是一個跨平台的組件,在 Linux、macOS、Windows 上也都可以直接原生的運行。
在 Kubernetes 的環境里,JuiceFS 提供了原生的 CSI Driver,可以直接通過 Kubernetes 的 PV 或 PVC 的方式直接 mount 到 pod 里。最後就是一些更高級的特性,比如說數據快取、加密、壓縮、回收站、配額等,目前 JuiceFS 的開源社區里也有很多的團隊和公司已經在生產環境中使用,例如小米、理想汽車、Shopee、知乎、火山引擎、網易遊戲、攜程等。
上圖主要分了三塊,一個是 Metadata Engine 也就是文件系統的元數據引擎,所謂元數據引擎就是要存儲整個文件系統的元資訊,比如文件的名字、大小以及許可權資訊和目錄結構等。這裡 JuiceFS 更希望和一些成熟的開源的並且大家日常會使用到的資料庫做結合,所以上圖列舉了一些常用的資料庫,都可以作為 JuiceFS 的元數據引擎。
Data Storage 部分是 JuiceFS 底層需要依賴的一個數據存儲,我們沒有重複造輪子,而是選擇了站在已有存儲的肩膀上。雲上的話對象存儲是一個非常好的基礎設施,大家都知道它有很多好處,例如低成本、高吞吐、高可用性。如果你是在 IDC 或者機房裡面也可以有類似的基礎設施提供,JuiceFS 作為使用方,可以直接把這些 Data Storage 對接上,並原生地把它作為整個文件系統的底層數據存儲。
最上面的話就是客戶端(Client),也就是 JuiceFS 的用戶會直接接觸到的這部分。通過不同的介面,讓用戶在不同的環境與不同的業務裡面都可以訪問到 JuiceFS,用戶不用擔心在不同的使用環境下會出現一些不一致的情況,只需要關心用哪個最熟悉的介面去訪問就好了。
上圖展示的是一個文件通過 JuiceFS 最終存儲到對象存儲上的一個流程,JuiceFS 會對一個文件做三個級別的拆分,就是最右邊這一列的 Chunk、Slice、Block 三個級別。
首先 JuiceFS 默認會按照固定的 64MB 大小,把一個文件按照這個粒度來拆分成很多的 Chunk,然後每個 Chunk 內部的話又可能會有很多這種不同個數、不同長度的 Slice 來構成,每個 Slice 最終又會由很多定長的 Block 來構成。Block 的大小用戶是可以配置的,默認情況下推薦使用 4MB 作為 Block 的大小。最終 Block 經過可選的比如壓縮或者加密之後,再上傳到對象存儲裡面,所以如果你直接去看對象存儲里存儲的數據的話,是不會看到原始文件的。比如說你的原始文件可能是 1G 大小的文件,但其實在對象存儲上去看的話,會看到很多小的 4MB 的 Block。
需要特別指出的一點是如果文件本身就是小於 4MB 的,比如一張圖片只有 100KB,這時 JuiceFS 是不會補齊到 4MB 的,還是會按照它原始的大小,文件是 100KB 最終存儲到對象存儲上也還是 100KB,不會補齊,不會佔用額外的空間。
最後講一下 JuiceFS 為什麼要對文件存儲格式去做分級。首先是需要基於對象存儲來支援一些高級的特性,比如說隨機寫入;其次對於不同的讀寫訪問模式,通過分塊之後也可以提升性能,比如說在並發寫入或並發讀取上能夠做到更好的性能優化。
JuiceFS 與 HDFS、對象存儲的比較
從存儲規模上來說,其實大家都知道 HDFS 的 NameNode 在單 namespace 上是有存儲上限的,一般來說到億級別這個量級就差不多了,但如果你要存儲更多的數據,你可能要做 federation 或者說一些其它的方式去擴展。對於對象存儲和 JuiceFS 來說,是可以非常輕鬆的支撐到百億級甚至更大的存儲規模。
然後在一致性上對於文件系統來說,之前不論使用 HDFS 或者說其它的文件系統,默認情況下,都是希望文件系統提供的是強一致性的保證。但是因為對象存儲的興起之後,會發現最終一致性反而會是一個更常見的情況。不過目前也有一些對象存儲,比如 S3 已經支援了強一致性。
在容量管理上 HDFS 是需要手動擴縮容的方式,所以你沒有辦法在雲上做一個非常彈性的容量管理,但是反觀對象存儲和 JuiceFS 的話,在容量管理上是可以做到非常彈性的,按量付費,大幅節約了存儲成本。
其它幾個特性對於大數據場景也是比較關鍵的,比如說原子重命名、List 性能、隨機寫、並發寫等,這些特性對於傳統的 HDFS 都是默認支援的,但對於對象存儲來說,有些特性它是部分支援的,有些特性完全無法支援。因為 JuiceFS 本身是一個完備的文件系統,所以這些特性都是具備的。
快取加速這塊,其實在 HDFS 或對象存儲上目前都還是不具備的一個功能,需要結合一些外部組件來實現,但 JuiceFS 本身已經內置了這個特性。
最後就兼容性來說,對象存儲可以用一些社區的組件去通過 HDFS 的 API 訪問,但目前暫時無法做到完全的兼容。包括 POSIX 這塊,雖然你可以用到如 S3FS 或者一些其它組件以 POSIX 的介面來訪問對象存儲,但它也只能達到一個部分兼容的狀態。對於 JuiceFS 來說是完全兼容 HDFS 和 POSIX 介面的。
這裡我們拿 HDFS 裡面的一個組件 NNBench 做了元數據的性能比較,上圖對比的是元數據請求延遲,越低越好。可以看到對象存儲與 HDFS、JuiceFS 來相比的話,在延遲上是可以差到一個甚至多個數量級的,這個也很好理解,元數據請求對於對象存儲來說本身就是比較大的開銷。反過來看 JuiceFS 和 HDFS 的話,其實是可以做到旗鼓相當的性能表現的。
另一個對比是元數據請求吞吐,越大越好,在某些場景下 JuiceFS 甚至可以做到相較 HDFS 有更好的性能表現,而對象存儲則會相差很多。
JuiceFS 與 Lakehouse
通過觀察 Lakehouse 的特徵,我們首先發現 Lakehouse 對於文件系統的依賴依然是存在的,如上文提到的 List 性能、原子重命名、並發寫、強一致性等。其次,對象存儲在使用上是有一些限制的,比如對象存儲基於 key 前綴的請求限制,也包括對象存儲的 API 請求是有成本的,特別是在大數據場景,API 請求成本還是蠻高的。最後就是快取加速對於性能的影響。
Lakehouse 對文件系統的依賴
首先我們看一下 Lakehouse 對於文件系統的依賴。這裡可以看下面這個表格,這個表格是直接從 Hudi 的官方文檔裡面摘抄過來的,Hudi 社區之前統計過直接用對象存儲,並根據不同的文件規模或者文件數來做 List 的性能比較。
可以看到從 100 到 100K,隨著文件數的增多,整個對象存儲 List 的開銷是逐步增大的,到後面已經變成了線性增長。所以在管理大量文件或者數據時,List 的性能開銷無法忽視。
反過來看 HDFS 或 JuiceFS 這類有獨立的元數據管理的文件系統,List 請求的開銷其實是非常小的,可以做到毫秒級甚至更快,微秒級都是有可能的。正因為整個文件系統元數據管理對 List 非常友好,在一個很短的時間內就可以完成整個目錄的 List。還有更多文件系統獨有的特性,比如原子重命名、並發寫、強一致性等都是非常關鍵的特性。
Specifically, Delta Lake relies on the following when interacting with storage systems:
- Atomic visibility: There must a way for a file to visible in its entirety or not visible at all.
- Mutual exclusion: Only one writer must be able to create (or rename) a file at the final destination.
- Consistent listing: Once a file has been written in a directory, all future listings for that directory must return that file.
上面這段話是從 Delta Lake 的官方文檔上摘抄過來的,在這裡就著重提到了對 Delta Lake 來說它依賴底層的存儲系統需要具備的幾個特性,比如說原子性,其中就包括並發寫、原子重命名等,然後是一致性的 Listing,這是對於文件系統強一致性的要求。同樣的,以上這些特性對於不管是 Hudi 或者 Iceberg 來說也都有類似的需求。所以對於文件系統的特性需求,在 Lakehouse 的組件上都屬於一個隱性的,或者說最基本的依賴,如果對象存儲或其它系統滿足不了某些特性的話就會帶來一些限制。比如說 Delta Lake 在用 S3 的時候,雖然可以並發讀數據,但是無法支援並發寫,只能在單個 Spark driver
里寫數據來保證事務。
對象存儲的 API 請求限制和成本
提到對象存儲的 API 請求限制和成本的話,這裡我們以 S3 為例,在 AWS 官方文檔上其實也已經明確告知用戶,針對每個 prefix(這裡 prefix 的定義就是存儲到 S3 上的每一個對象的 key 的前綴)的 GET 請求最大 QPS 是 5500,PUT 請求的最大 QPS 是 3500,對於常規應用而言這個請求限制其實是沒問題的,但是對於大數據場景來說,QPS 限制就會影響整體計算任務的性能甚至是穩定性了。對此 Iceberg 提出了一個優化方法是在 key 的最前面加上一個隨機的哈希值,目的就是為了分散請求的 prefix,使其不會那麼快地觸碰到針對單個 prefix 的 QPS 限制。
對於 JuiceFS 來說,設計上已經天然具備分散請求 prefix 的理念。因為所有的文件數據最終上傳到對象存儲的時候,都會被切分成 4MB 的 block 。每個 block 在對象存儲上的 key 其實是一個多級的 prefix 來構成,它不是一個單級的目錄結構。比如說 0/1/123_0_1024 這個 key,是根據每個 block 的 ID 做了兩級 prefix ,然後不同的 block 會分散到不同的 prefix 裡面來。
然後對於同一個文件來說,如果它是個大文件的話,它的所有 block 也是分布在不同的 prefix 裡面的。所以雖然看起來是訪問同一個文件,但是對於對象存儲來說你訪問的是不同的 prefix,所以這也是 JuiceFS 給文件分 block 的好處,也是對於對象存儲 API 請求限制的一個優化設計。
其次在對象存儲請求成本上,就 JuiceFS 而言,對於對象存儲 API 的依賴其實非常少,只有 GetObject
、PutObject
、DeleteObject
這三個 API,剩下的所有 API 都不依賴。所以接入 JuiceFS 數據存儲的存儲系統,只需要提供這三個 API 就夠了,所有的元數據請求都不會經過對象存儲,這部分的 API 請求成本就省掉了。
剛剛提到這三個 API 其實主要是用來讀寫或者刪除數據用的,其中比如 GetObject
是可以通過後面會提到的「快取加速」做進一步的優化,JuiceFS 會自動地把頻繁訪問的數據快取到本地,這樣能夠大幅減少熱數據對於對象存儲 API 請求的依賴。相比直接訪問對象存儲, API 請求成本會降低很多。
快取加速
第三點就是剛剛提到的快取加速,這裡我們拿一個 benchmark 為例,這個 benchmark 使用業界最常見的 TPC-DS 數據集,計算引擎用的是 Presto,數據採用了兩種格式,分別是 ORC 和 Parquet。
可以看到在快取充分預熱的情況下,JuiceFS 的整體性能表現是可以做到與 HDFS 相當的,所以這也是快取加速能夠體現的一些優勢,特別是在存算分離的架構下。
JuiceFS 與數據湖生態
首先 JuiceFS 社區給 Hudi 貢獻了一個 PR 可以在 Hudi 內原生支援 JuiceFS ,這個特性已經在 Hudi v0.10.0 及以上版本支援。具體使用方法可以參考 Hudi 的官方文檔。這裡只是拿 Hudi 舉了個例子,其實用 Iceberg、Delta Lake 結合 JuiceFS 也是類似的,JuiceFS 本身已經提供了 HDFS 完全兼容的 API,任何使用 HDFS 的地方都可以直接替換為 JuiceFS。
另外 JuiceFS 跟 AI 社區一個比較流行的開源組件 Fluid 也有一些結合。Fluid 是一個開源的以 Kubernetes 環境為主的數據集編排以及訪問加速的組件。目前它主要用在 AI 的場景,但是其實整個 Fluid 社區也想要跟大數據場景做一些結合。Fluid 主要由阿里雲的團隊以及南京大學的一些團隊來維護和開發,它也是 CNCF 里的一個沙盒項目。
JuiceFS 社區和雲知聲團隊一起給 Fluid 社區貢獻了一個 PR,把 JuiceFS 作為一個 runtime 集成到 Fluid 中。如果用 Fluid 來做 AI 模型訓練,就可以直接原生地使用 JuiceFS 作為其中的一個後端存儲或者說加速組件,幫助你更快地在 Kubernetes 里把模型訓練任務跑起來。大家有興趣的話,可以查看 Fluid 的官方文檔了解一下。
以上就是我今天的分享,感謝大家!
如有幫助的話歡迎關注我們項目 Juicedata/JuiceFS 喲! (0ᴗ0✿)