豆瓣成立於 2005 年,是中國最早的社交網站之一。在 2009 到 2019 的十年間,豆瓣資料平臺經歷了幾輪變遷,形成了 DPark + Mesos + MooseFS 的架構。
由機房全面上雲的過程中,原有這套架構並不能很好的利用雲的特性,豆瓣需要做一次全面的重新選型,既要考慮未來十年的發展趨勢,也需要找到與現有元件相容且平滑過渡的解決方案。一番改造後, 豆瓣資料平臺目前形成了 Spark + Kubernetes + JuiceFS 的雲上資料湖架構,本文將分享此次選型升級的整體歷程。
01 豆瓣早期資料平臺
在 2019 年,豆瓣所使用的資料平臺主要由以下元件構成:
Gentoo Linux,內部使用的 Linux 發行版;MooseFS ,分散式檔案系統;Apache Mesos 負責整個叢集的資源管理,以及 Dpark 作為分散式計算框架提供給開發者使用。
從上圖可以看到在這個資料平臺中,計算和儲存是一體的,每個計算任務是由 Mesos 進行排程的。計算任務的 I/O 操作都是透過 MooseFS 的 Master 獲取後設資料,並在本地獲取需要計算的資料。此外,GPU 計算叢集也是透過 Mesos 進行管理,不同的是, GPU 會基於視訊記憶體進行共享。
平臺元件介紹
Gentoo Linux
Gentoo Linux 是一個較為小眾的 Linux 發行版,具有幾乎無限制的適應性特性,是一個原發行版。Gentoo Linux 採用滾動更新的方式,所有軟體包都直接從社群中獲取二進位制包,我們則透過原始碼構建我們所需的軟體包。Gentoo Linux 有一個強大的包管理器,使用它也會帶來很多便利,也同時存在一些問題。比如,滾動更新的速度非常快,但對於伺服器來說,可能存在一定的不穩定性。
使用原始碼構建軟體包的好處是當社群沒有預編譯好我們所需的軟體包時,我們可以非常簡單地構建出自己所需的軟體包,並且當已有的軟體包無法滿足我們的需求時,也可以很容易地進行定製調整。但這也會帶來較高的維護成本。
另外,如果所有軟體包都能按照規範進行編寫的話,依賴衝突問題幾乎是不存在的,因為在打包過程中就已經可以發現。但實際情況是並不是所有軟體包都能遵守一個好的依賴描述的約定,因此依賴衝突問題可能仍然存在。
Gentoo Linux 是較為小眾的選擇,儘管社群質量很高,但是使用者也比較少,一些新專案可能沒有使用者進行足夠的測試,我們在實際使用過程中會遇到各種各樣的問題。這些問題大部分需要我們自己解決,如果等待其他人回覆的話,響應會比較慢。
MooseFS
MooseFS 是一個開源的、符合 POSIX 標準的分散式檔案系統,它只使用 FUSE 作為 I/O 介面,並擁有分散式檔案系統的標準特性,如容錯、高可用、高效能和可擴充套件性。
對於幾乎所有需要使用標準檔案系統的場景,我們都使用 MooseFS 作為替代品,並在其基礎上開發了一些自己的小工具。例如,我們可以直接使用分散式檔案系統來處理 CDN 的回源。在早期版本中,MooseFS 沒有主節點的備份功能,因此我們開發了一個 ShadowMaster 作為後設資料的熱備節點,並編寫了一些分析 MooseFS 後設資料的工具,以解決一些運維問題。作為一個儲存設施,MooseFS 整體比較穩定,並且沒有出現重大的問題。
Apache Mesos
Mesos 是一個開源的叢集管理器,與YARN 有所不同,它提供公平分配資源的框架,並支援資源隔離,例如 CPU 或記憶體。Mesos 早在 2010 年就被 Twitter 採用, IBM 在 2013 年開始使用。
Dpark
由於公司全員使用 Python,因此使用了 Python 版的 Spark,即 Dpark,它擴充套件了RDD API,並提供了 DStream。
公司內部還開發了一些小工具,例如 drun 和 mrun,可以透過 Dpark 將任意 Bash 指令碼或資料任務提交到 Mesos 叢集,並支援 MPI 相關的任務提交。Dgrep 是用於快速查詢日誌的小工具,JuiceFS 也提供了類似的工具。雖然 Dpark 本身可以容器化,但公司主要的資料任務是在物理伺服器上執行的。支援容器化可以讓場內任務更好地利用線上業務的模型程式碼。
02 平臺演進的思考
在 2019 年,公司決定將基礎設施轉移到雲端並實現計算和儲存分離,以提高平臺的靈活性。由於以前的計算任務在物理機上執行,隨著時間的推移,出現了越來越多的依賴衝突問題,維護難度不斷增加。
同時,公司希望內部平臺能夠與當前的大資料生態系統進行互動,而不僅僅是處理文字日誌或無結構化、半結構化的資料。此外,公司還希望提高資料查詢效率,現有平臺上儲存的資料都是行儲存,查詢效率很低。最終,公司決定重新設計一個平臺來解決這些問題。
平臺演進時,我們沒有非常強的相容性需求。只要成本收益合理,我們就可以考慮將整個平臺替換掉。這就像是環法腳踏車比賽中,如果車有問題就會考慮換車,而不是隻換輪子。在更換平臺時,我們如果發現現有平臺的任務無法直接替換,可以先保留它們。在切換過程中,我們有以下主要需求:
- Python 是最優先考慮的開發語言。
- 必須保留 FUSE 介面,不能直接切換到 HDFS 或者 S3。
- 儘可能統一基礎設施,已經選用了部分 Kubernetes,就放棄了 Mesos 或其他備選項。
- 新平臺的學習成本應儘可能低,讓資料組和演算法組的同事能夠以最低的成本切換到新的計算平臺上。
03 雲上構建資料平臺
目前的雲上資料平臺幾乎是全部替換了,Gentoo Linux 的開發環境變變成了 Debian based container 的環境, MooseFS 是換用了現在的 JuiceFS,資源管理使用了 Kubernetrs,計算任務的開發框架使用了 Spark,整體進行了徹底替換的,其他的設施是在逐漸縮容的過程,還會共存一段時間。
JuiceFS 作為統一儲存資料平臺
為了更好地滿足不同的 I/O 需求和安全性考慮,我們會為不同的使用場景建立不同的 JuiceFS 卷,並進行不同的配置。JuiceFS 相對於之前的 MooseFS,建立檔案系統更加簡單,實現了按需建立。除了 SQL 資料平臺外,我們的使用場景基本上都是由 JuiceFS 提供的服務。
在 JuiceFS 中,資料有幾種型別:線上讀寫、線上讀取離線寫入、線上寫入離線讀取、離線讀寫。
所有的讀寫型別都在 JuiceFS 上進行,比如日誌匯聚到卷中,Spark 可能會讀取並進行 ETL,然後將資料寫入資料湖。此外,從 Kafka 資料來源讀取的資料也會透過 Spark 進行處理並寫入資料湖。
Spark 的 Check Point 直接儲存在另一個 JuiceFS 卷中,而資料湖的資料則直接提供給演算法組的同學進行模型訓練,並將訓練結果透過 JuiceFS 寫回。我們的運維團隊則透過各種指令碼或工具來管理 JuiceFS 上的檔案生命週期,包括是否對其進行歸檔處理等。因此,整個資料在 JuiceFS 中的流轉過程大致如上圖所示。
新資料平臺元件介紹
Debian based container
首先,運維團隊選擇了 Debian based container 作為基礎映象,我們就直接使用了。我們的計算平臺的映象很大,為了解決任務啟動速度的問題,團隊在每個節點上預拉取了映象。
JuiceFS
切換到 JuiceFS 儲存系統時,使用者感受不到變化,JuiceFS 非常穩定。JuiceFS 比 MooseFS 更好的一點是,它擁有 HDFS 的 SDK,方便了團隊將來切換到 Spark 等工具。團隊在 Kubernetes 上使用了 JuiceFS CSI,直接實現了 KV 儲存的情況,按需建立 volume 也很方便。JuiceFS 團隊溝通高效,解決問題迅速。例如,當 stream 的 checkpoint 頻率太高時,JuiceFS 團隊早早通知並迅速解決。
Kubernentes
我們早在 1.10 版本的時候就開始試用 Kubernetes。後來豆瓣對外的服務叢集在 1.12 版本開始逐步遷移到 Kubernetes,基本上是在現有機器上完成了原地的替換。計算叢集則是在上雲後開始搭建的,基於1.14 版本。我們在版本升級方面可能比其他公司更為激進,目前我們的 Kubernetes 版本已經升級到了1.26 版。
我們選擇 Kubernetes 作為計算平臺的原因之一是它有比較統一的元件。此外,透過 scheduling framework 或者 Volcano,我們可以影響它的排程,這是我們比較希望擁有的一個特性。
我們還可以利用社群的 Helm 非常快速地部署一些需要的東西,比如 Airflow、Datahub 和 Milvus 等服務,這些服務都是透過 Helm 部署到我們的離線 Kubernetes 叢集中提供的。
Spark
在最開始測試 Spark 時,我們像使用 Dpark 一樣將任務執行在 Mesos 叢集上。之後我們選定了 Kubernetes,使用 Google Cloud Platform 上的 spark-on-k8s-operator 將 Spark 任務部署到 Kubernetes 叢集中,並部署了兩個 Streaming 任務,但並未進行大規模的部署。
隨後,我們確定了使用 Kubernetes 和 Airflow,計劃自己實現一個 Airflow Operator,在 Kubernetes 中直接提交 Spark 任務,並使用 Spark 的 Cluster Mode 將任務提交到 Kubernetes 叢集中。
對於開發環境,我們使用 JupyterLab 進行開發。廠內有一個 Python 庫對 Spark Session 進行了一些小的預定義配置,以確保 Spark 任務能夠直接提交到 Kubernetes 叢集上。
目前,我們使用 Kubernetes Deployment 直接部署 Streaming 任務,這是一個很簡單的狀態,未來可能會有一些改進的地方。另外,我們正在準備試用 Kyuubi & Spark Connect 專案,希望能夠為線上任務提供更好的讀寫離線資料的體驗。
我們的版本升級非常激進,但確實從社群中獲益匪淺。我們解決了日常計算任務中許多常見的最佳化場景。我們激進升級的原因是希望能夠儘可能多地利用社群的資源,提供新特性給開發者。但我們也遇到了問題,例如 Spark 3.2 的 parquet zstd 壓縮存在記憶體洩漏。為了規避這個問題,我們提前引入了未釋出的補丁。
現在,我們使用兩種方式來讀寫 JuiceFS 資料:FUSE 和 HDFS。FUSE 主要用於 ETL 任務,例如讀寫日誌和 CSV 檔案。我們也會將 Hive 錶轉存為 CSV 檔案下載供未切換到 Spark 的任務進行計算。其他的資料,則直接透過預先配置好的 HDFS(如 Hive Table 和 Iceberg Table)進行讀寫,這大大簡化了我們的工作。
在資料湖的選擇上,我們一開始考慮了 Delta Lake,但由於它不支援 Merge on Read,在目前的使用場景存在寫放大,我們放棄了它。取而代之,我們選擇了 Iceberg,並將其用於 MySQL CDC 處理。我們將資料直接儲存在 JuiceFS 上進行讀寫,並且目前沒有遇到任何效能上的問題。未來,如果我們需要擴大規模使用,可能需要與 JuiceFS 的團隊溝通一下,看看有哪些最佳化措施。
04 收穫與展望
我們切換到新的計算平臺之後,獲得了很多原來沒有的功能。例如,我們現在可以使用基於 SQL 的大量任務,這些任務的效能比以前好得多,各種報表的實時性也更好了。
與 Mesos 的情況不同,Spark 宣告瞭多少資源就使用多少資源,這與以前的 Dpark 相比有很大的差異,因為以前大家都是公平分享,相互之間會有影響。現在,每個任務的執行時間都比較可預測,任務評估也比較容易預測,整個新平臺對於業務資料的讀取也有更好的時效性。
以前的歷史包袱是相當沉重的,現在我們已經趕上了社群的步伐。去年年末的各種統計和排名都已經遷移到了新的計算平臺上,並且執行非常穩定。
我們正在優先考慮採取一些成本下降措施,以實現整個計算叢集的動態擴縮容。我們正積極努力實現此目標,並希望提供更加穩定的 SQL 介面。為此,我們計劃採用支援 Multi-tenant 的 SQL 伺服器,並嘗試引入 Spark 3.4 的最新特性。
長遠來看,我們希望透過 Spark Remote Shuffle Service 進一步實現存算分離,以便更有效地利用資源。也許未來我們會開發一個“Spark as a Service”,提供給開發者使用。總之,我們正在追趕社群的步伐,並不斷努力提升我們的技術水平。
如有幫助的話歡迎關注我們專案 Juicedata/JuiceFS 喲! (0ᴗ0✿)