一面資料原有的技術架構是線上下機房中使用 CDH 構建的巨量資料叢集。自公司成立以來,每年都保持著高速增長,業務的增長帶來了資料量的劇增。
在過去幾年中,我們按照每 1 到 2 年的規劃擴容硬體,但往往在半年之後就不得不再次擴容。而每次擴容都需要花費大量精力。
為了解決包括擴容週期長、計算儲存資源不匹配以及高昂的運維成本等這些問題,我們決定對資料架構進行改造,並將資料遷移到雲端,採用存算分離的結構。 在這個案例中,我們將為大家介紹 Hadoop 上雲的架構設計、選型的思考、元件評估以及資料遷移的整個過程。
目前,基於JuiceFS 我們實現了計算和儲存分離的架構,總儲存量增加了2倍;效能方面的變化無明顯感知,運維成本大幅降低。在案例的末尾還附上了針對阿里雲 EMR 以及 JuiceFS 的一手運維經驗,希望這個案例能為其他面臨類似問題的同行提供有價值的參考
為了滿足業務需求,一面資料抓取了國內外數百個大型網站的資料,目前數量已經超過 500 個,並積累了大量的原始資料、中間資料和結果資料。隨著我們不斷增加抓取的網站數量和服務的客戶群,資料量也在快速增長。因此,我們著手開始進行擴容以滿足需求的增長。
原有的架構是在一個線下機房使用 CDH 構建了一個巨量資料叢集。如下圖所示,我們主要使用了 Hive、Spark 和 HDFS 等元件。在 CDH 的上游有多種資料生產系統,在這裡只列出了Kafka,因為與 JuiceFS 相關;除了Kafka之外,還有其他一些儲存方式,包括 TiDB、HBase、MySQL 等等。
資料流向方面,我們有一個上游的業務系統和資料採集系統,資料會被採集下來後寫入 Kafka。然後我們使用一個 Kafka Connect 叢集,將資料同步到 HDFS。
在這個架構上方,我們使用了一個自研的資料開發平臺,稱為 OneWork,用於開發和管理各種任務。這些任務會通過 Airflow 下發到任務佇列進行排程。
業務/資料會增長比較快,業務擴容週期長。公司在 2016 年線下機房部署了 CDH 叢集,到 2021 年已儲存和處理 PB 級的資料。公司自創立以來一直保持每年翻一番的高增長,而比業務量增長更快的是 Hadoop 叢集的資料量。在這幾年間,按 1 到 2 年規劃的硬體,往往因資料增長超出預期而在半年後不得不再次擴容。每次擴容週期可達到一個月,除了花費大量精力跟進行政和技術流程,業務端也不得不安排較多人日控制資料量。如果選擇購買硬碟和伺服器來進行擴容,實施週期會相對較長。
儲存計算耦合,容量規劃難,容易錯配。傳統的 Hadoop 架構中,儲存和計算是緊密耦合的,難以根據儲存或計算的需求獨立進行擴容和規劃。舉個例子,假設我們需要擴容儲存,於是首先需要購買一批新的硬碟,同時連帶著需要購買計算資源。在最初時,計算資源可能會變得過剩,因為可能實際不需要那麼多的計算資源,從而一定程度上導致了超前投資。
CDH 版本比較老,不敢升級。 我們因為叢集也建的比較早了,為了穩定,也就不敢升級了。
運維成本較高(全公司僅1個全職運維)公司當時有200多個人,只有一個運維,這意味著運維工作的工作量很大。因此,我們希望能夠採用更穩定、更簡單的架構來提供支援。
機房存在單點風險。考慮到長遠的因素,所有的資料都儲存在同一個機房中,這存在一定的風險。例如,如果光纜被挖斷,這種情況經常發生,那麼我們僅有一個機房仍然會面臨單點故障的風險。
考慮到這些因素和挑戰,我們決定進行一些新的改變。以下是我們考慮架構升級的一些主要維度。
最終選擇的方案是使用「阿里雲 EMR + JuiceFS + 阿里雲 OSS」 來搭建存算分離的巨量資料平臺,將雲下資料中心的業務逐步遷移上雲。
這個架構使用物件儲存來替代 HDFS,並選擇了 JuiceFS 作為協定層,因為JuiceFS 相容 POSIX 和 HDFS 協定。在頂部,我們使用了雲上半托管的 Hadoop 解決方案 EMR。它包含了很多 Hadoop 相關的元件,例如 Hive、Impala、Spark、Presto/Trino 等等。
首先是決定使用哪家雲廠商。由於業務需求,AWS、Azure 和阿里雲都有在用,綜合考慮後認為阿里雲最適合,有這些因素:
阿里雲的 EMR 本身也有使用 JindoFS 的存算分離方案,但基於以下考慮,我們最終選擇了JuiceFS:
JuiceFS 使用 Redis 和物件儲存為底層儲存,使用者端完全是無狀態的,可以在不同環境存取同一個檔案系統,提高了方案的靈活性。而 JindoFS 後設資料儲存在 EMR 叢集的本地硬碟,不便於維護、升級和遷移。
直接擷取官方檔案的介紹:
JuiceFS 是一款面向雲原生設計的高效能共用檔案系統,在 Apache 2.0 開源協定下發布。提供完備的 POSIX 相容性,可將幾乎所有物件儲存接入本地作為海量本地磁碟使用,亦可同時在跨平臺、跨地區的不同主機上掛載讀寫。
JuiceFS 採用「資料」與「後設資料」分離儲存的架構,從而實現檔案系統的分散式設計。使用 JuiceFS 儲存資料,資料本身會被持久化在物件儲存(例如,Amazon S3),相對應的後設資料可以按需持久化在 Redis、MySQL、TiKV、SQLite 等多種資料庫中。
除了 POSIX 之外,JuiceFS 完整相容 HDFS SDK,與物件儲存結合使用可以完美替換 HDFS,實現儲存和計算分離。
PoC 的目的是快速驗證方案的可行性,有幾個具體目標:
期間做了大量測試、檔案調研、內外部(阿里雲 + JuiceFS 團隊)討論、原始碼理解、工具適配等工作,最終決定繼續推進。
我們在 2021 年 10 月開始探索 Hadoop 的上雲方案;11 月做了大量調研和討論,基本確定方案內容;12 月和 2022 年 1 月春節前做了 PoC 測試,在春節後 3 月份開始搭建正式環境並安排遷移。為了避免導致業務中斷,整個遷移過程以相對較慢的節奏分階段執行, 遷移完後,雲上的 EMR 叢集資料量預計會超過單副本 1 PB.
做完技術選型之後,架構設計也能很快確定下來。考慮到除了 部分業務仍然會保留在資料中心的 Hadoop 叢集,所以整體實際上是個混合雲的架構。
整體架構大致如上圖所示:左側是的線下機房,使用了傳統的 CDH 架構和一些 Kafka 叢集。右側是部署在阿里雲上的 EMR 叢集。這兩部分通過一條高速專線進行連線。頂部是 Airflow 和 OneWork,由於都支援支援分散式部署,因此可以輕鬆進行水平擴充套件。
挑戰1: Hadoop 2 升到 Hadoop 3
我們 CDH 版本比較老,也不敢升級,但我們既然做了遷移,肯定還是希望新叢集能夠升級到新版本。在遷移過程中,需要注意 HDFS 2 和 3 之間的差異,介面協定和檔案格式有可能會發生變化。JuiceFS 完美相容 HDFS 2 & 3,很好地應對了這個挑戰。
挑戰2: Spark 2 升級到 Spark 3
Spark 的一個升級對我們影響是比較大的,因為有不少不相容的更新。這就意味著原來在 Spark 2 上面寫的程式碼需要完成修改才能適配到新的版本里面去。
**挑戰3: Hive on Spark 不支援 Spark 3 **
在機房環境中,預設使用的是 CDH 自帶的 Hive on Spark,但當時 CDH 中的 Spark 版本只有 1.6。我們在雲上使用的是 Spark 3,而 Hive on Spark 並不支援 Spark 3,這導致我們無法繼續使用 Hive on Spark 引擎。
經過調研和測試,我們將 Hive on Spark 改為了 Hive on Tez。這個改動相對來說還比較容易,因為 Hive 本身對於不同的計算引擎提供了抽象和適配,所以對於我們的上層程式碼改動較小。Hive on Tez 在效能上可能略慢於 Spark。此外,我們也關注國內網易開源的一個新計算引擎 Kyuubi,它相容 Hive,並提供了一些新特性。
挑戰4: Hive 1 升級到 Hive 3,後設資料結構有變化
對於 Hive 升級來說,最主要的影響之一是後設資料結構的變化,因此在遷移過程中,我們需要進行資料結構的轉換。因為無法直接使用Hive來處理這種遷移,所以我們需要開發相應的程式來進行資料結構的轉換。
挑戰5:許可權管理由 Sentry 替換為 Ranger
這是一個比較小的問題,就是我們之前使用 Sentry 做許可權管理,這個社群不怎麼活躍了,EMR 也沒有整合,所以就替換為 Ranger。
除了技術挑戰外,更大的挑戰來自與業務端。
業務挑戰1:涉及的業務多,不能影響交付
我們擁有多個業務,涉及不同的網站、客戶和專案。由於業務交付不能中斷,遷移過程必須進行分業務處理,採用漸進式遷移的方式。
遷移過程中,資料的變動會對公司的多個環節產生影響,例如 ETL 資料倉儲、資料分析師、測試和產品開發等。因此,我們需要進行良好的溝通和協調,制定專案管理計劃和排期。
業務挑戰2: 資料表、後設資料、檔案、程式碼多
除了資料,我們在上層還有許多業務程式碼,包括資料倉儲的程式碼、ETL 的程式碼以及一些應用程式的程式碼,如 BI 應用需要查詢這些資料。
要遷移的資料包括兩部分:Hive Metastore 後設資料以及 HDFS 上的檔案。由於不能中斷業務,採用存量同步 + 增量同步(雙寫)的方式進行遷移;資料同步完後需要進行一致性校驗。
存量同步
對於存量檔案同步,可以使用 JuiceFS 提供的功能完整的資料同步工具 sync 子命令 來實現高效遷移。JuiceFS sync 命令支援單節點和多機並行同步,實際使用時發現單節點開多執行緒即可打滿專線頻寬,CPU 和記憶體佔用低,效能表現非常不錯。需要注意的是,同步過程中 sync 命令會在本地檔案系統寫快取,因此最好掛載到 SSD 盤來提升效能。
Hive Metastore 的資料同步則相對麻煩些:
dbs
表的 DB_LOCATION_URI
和 sds
表的 LOCATION
)因此我們開發了一套指令碼工具,支援表和分割區粒度的資料同步,使用起來很方便。
增量同步
增量資料主要來自兩個場景:Kafka Connect HDFS Sink 和 ETL 程式,我們採用了雙寫機制。
Kafka Connect 的 Sink 任務都複製一份即可,設定方式上文有介紹。ETL 任務統一在 OneWork 上開發,底層使用 Airflow 進行排程。通常只需要把相關的 DAG 複製一份,修改叢集地址即可。實際遷移過程中,這一步遇到的問題最多,花了大量時間來解決。主要原因是 Spark、Impala、Hive 元件版本的差異導致任務出錯或資料不一致,需要修改業務程式碼。這些問題在 PoC 和早期的遷移中沒有覆蓋到,算是個教訓。
資料校驗
為了能讓業務放心的使用新的架構,資料校驗必不可少。 資料同步完後需要進行一致性校驗,分三層:
檔案一致。在存量同步階段做校驗,通常的方式是用 checksum. 最初的 JuiceFS sync 命令不支援 checksum 機制,我們建議和討論後,JuiceFS 團隊很快就加上了該功能(issue,pull request)。除了 checksum,也可考慮使用檔案屬性對比的方式:確保兩個檔案系統裡所有檔案的數量、修改時間、屬性一致。比 checksum 的可靠性稍弱,但更輕量快捷。
後設資料一致。有兩種思路:對比 Metastore 資料庫的資料,或對比 Hive 的 DDL 命令的結果。
計算結果一致。即使用 Hive/Impala/Spark 跑一些查詢,對比兩邊的結果是否一致。一些可以參考的查詢:表/分割區的行數、基於某個欄位的排序結果、數值欄位的最大/最小/平均值、業務中經常使用的統計聚合等。
資料校驗的功能也封裝到了指令碼裡,方便快速發現資料問題。
遷移完業務穩定執行後,我們開始考慮分級儲存。分級儲存在各種資料庫或儲存系統中都是一個常見問題,資料存在冷熱區別,而儲存媒介的價格也存在差異,因此我們希望將冷資料儲存在更便宜的儲存媒介上以控制成本。
在之前的 HDFS 中,我們已經實施了分級儲存策略,購買了兩種型別的硬碟,將熱資料儲存在高速硬碟中,將冷資料儲存在低速硬碟中。
然而,JuiceFS 為了優化效能採取的資料分塊模式,會對分級儲存帶來限制。 按照 JuiceFS 的處理,當檔案儲存在物件儲存上時,它被邏輯上拆分為許多 chunks、slices 和 blocks,最終以 block 的形式儲存在物件儲存中。
因此,如果我們觀察物件儲存中的檔案,實際上無法直接找到檔案本身,而只能看到被分割成的小塊。即使 OSS 提供了宣告週期管理功能,但我們也無法基於表、分割區或檔案級別進行生命週期的設定。
後續我們通過以下這種方式來解決。
兩個 bucket:標準( JuiceFS ) + 低頻(OSS): 建立兩個儲存桶,一個儲存桶用於JuiceFS,並將所有資料儲存在標準儲存層中。另外,我們額外建立一個低頻的OSS儲存桶。
基於業務邏輯,對錶/分割區/檔案,設定儲存策略表。 我們可以根據表、分割區或檔案來設定儲存策略,並編寫定時任務來掃描並執行這些策略。
用Juicesync 將低頻檔案從 JuiceFS 匯出到 OSS 並修改 Hive 後設資料。 檔案從 JuiceFS 轉移到 OSS 之後會從 JuiceFS 刪除,並且在 OSS 上能看到完整的檔案內容,我們就可以對其設定生命週期規則。轉移完檔案後需要及時修改 Hive 後設資料,,將 Hive 表或分割區的位置更改為新的OSS地址。EMR 的 Hive/Impala/Spark 等元件原生支援 OSS,因此應用層基本無感(需注意存取低頻檔案會帶來額外開銷)。
完成這個操作後,除了實現分級儲存以降低成本外,還有一個額外的好處是我們可以減少JuiceFS後設資料的數量。因為這些檔案不再屬於 JuiceFS,而是由 OSS 直接管理,這意味著JuiceFS 中的 inode 數量會減少,後設資料的管理壓力就會減輕,Redis請求的數量和容量也會降低。從穩定性的角度來看,這對系統會更有利。
存算分離的收益
總的儲存量增長了兩倍,計算資源不動,偶爾開啟臨時的任務節點。在我們的場景中,資料量增長非常快,但查詢需求相對穩定。從 2021 年至今,資料量已增長兩倍。計算資源在初始階段至今基本沒有做過太多的改動,除非出於某些業務需求需要更快的計算速度,我們會開啟彈性資源和臨時任務節點來加速。
效能變化
在我們的業務場景中,主要是進行巨量資料的批次處理離線計算,總體而言對於效能的延遲並不敏感。在 PoC 期間,我們進行了一些簡單的測試。然而,這些測試很難準確說明問題,因為測試過程受到了許多影響因素的影響。我們首先更換了儲存系統,從 HDFS 切換到了 JuiceFS,同時進行了元件版本升級,Hive 引擎也發生了變化。此外,叢集負載也無法完全一致。在我們的場景中,與之前在物理伺服器上部署的 CDH 相比,叢集架構的效能差異並不明顯。
用性 & 穩定性
**實施複雜度 **
當評估類似架構或方案的複雜度時,有許多影響因素需要考慮。其中包括業務場景的差異,以及對延遲要求的敏感程度不同。此外,表資料量的規模也會產生影響。在我們的場景中,我們有大量的表和資料庫,檔案數量相對較多。此外,上層應用程式的特性、使用業務的數量以及相關程式等也會對複雜度產生影響。另一個重要的影響因素是版本遷移的逐漸差異。如果只進行平移而保持版本不變,那麼元件的影響基本上可以消除。
配套工具和儲備是一個重要的影響因素。在進行數倉或 ETL 任務時,有多種實現方式可供選擇,例如手動編寫 Hive SQL 檔案、Python 或 Java 程式,或者使用常見的排程工具。但無論採用哪種方式,我們都需要複製和修改這些程式,因為雙寫是必要的。
我們使用自研的開發平臺 OneWork,在任務設定方面非常完善。通過 OneWork 平臺,使用者可以在 Web 介面上設定這些任務,從而實現統一管理。Spark 任務的部署也無需登入到伺服器上操作,OneWork 會自動提交到 Yarn 叢集。這個平臺大大簡化了程式碼設定和修改的過程。我們編寫了一個指令碼將任務設定複製出來,進行一些修改,就可以實現高度的自動化程度,幾乎達到百分之八九十,從而順利執行這些任務。
後續計劃大致有幾個方向:
關於 IDC-阿里雲專線:
能提供專線服務的供應商很多,包括 IDC、阿里雲、運營商等,選擇的時候主要考慮線路質量、成本、施工週期等因素,最終我們選擇了IDC的方案。IDC 跟阿里雲有合作,很快就完成了專線的開通。這方面如果遇到問題,可以找 IDC 和阿里雲的支援。除專線租用成本,阿里雲也會收取下行(從阿里雲到 IDC)方向傳輸費用。專線兩端的內網 IP 完全互通,阿里雲和 IDC 兩側都需要一些路由設定。
關於 EMR Core/Task 節點型別的選擇:
JuiceFS 可以使用本地硬碟做快取,能進一步減少 OSS 頻寬需求並提高 EMR 效能。更大的本地儲存空間,可以提供更高的快取命中率。
阿里雲本地 SSD 範例是較高價效比的 SSD 儲存方案(相對於雲盤),用作快取正合適。
JuiceFS 社群版未支援分散式快取,意味著每一個節點都需要一個快取池,所以應該選用盡量大的節點。
基於以上考慮和設定對比,我們決定選用 ecs.i2.16xlarge,每個節點 64 vCore、512GiB Memory、1.8T*8 SSD。
關於 EMR 版本:
軟體方面,主要包括確定元件版本、開啟叢集、修改設定。我們機房使用的是 CDH 5.14,其中 Hadoop 版本是 2.6,阿里雲上最接近的版本是 EMR 3.38. 但調研時發現該版本的 Impala 和 Ranger 不相容(實際上我們機房使用的是 Sentry 做許可權管理,但 EMR 上沒有),最終經過評估對比,決定直接使用 EMR 5 的最新版,幾乎所有元件的大版本都做了升級(包含 Hadoop 3、Spark 3 和 Impala 3.4)。此外,使用外部 MySQL 作為 Hive Metastore、Hue、Ranger 的資料庫。
關於 JuiceFS 設定:
基本參考JuiceFS官方檔案《在 Hadoop 中通過 Java 使用者端存取 JuiceFS》即可完成設定。另外我們也設定了這些引數:
juicefs.cache-dir
快取目錄。這個引數支援萬用字元,對多個硬碟的範例環境很友好,如設定為/mnt/disk*/juicefs-cache
(需要手動建立目錄,或在EMR節點初始指令碼中建立),即用全部本地 SSD 作為快取。另外也要關注 juicefs.cache-size
、juicefs.free-space
兩個引數。juicefs.push-gateway
:設定一個 Prometheus Push Gateway,用於採集 JuiceFS Java 使用者端的指標。juicefs.users
、juicefs.groups
:分別設定為 JuiceFS 中的一個檔案(如 jfs://emr/etc/users
、jfs://emr/etc/groups
),解決多個節點 uid 和 gid 可能不統一的問題。關於 Kafka Connect 使用 JuiceFS:
經過一些測試,確認 JuiceFS 可以完美應用於 Kafka Connect 的 HDFS Sink 外掛(我們把設定方式也補充到了官方檔案)。相比使用 HDFS Sink 寫入HDFS,寫入 JuiceFS 需要增加或修改以下設定項:
將 JuiceFS Java SDK 的 JAR 包釋出到 Kafka Connect 每一個節點的 HDFS Sink 外掛目錄。Confluent 平臺的外掛路徑是:/usr/share/java/confluentinc-kafka-connect-hdfs/lib
編寫包含 JuiceFS 設定的 core-site.xml
,釋出到 Kafka Connect 每一個節點的任意目錄。包括這些必須設定的專案:
fs.jfs.impl = io.juicefs.JuiceFileSystem
fs.AbstractFileSystem.jfs.impl = io.juicefs.JuiceFS
juicefs.meta = redis://:[email protected]:6379/1
請參見 JuiceFS Java SDK 的設定檔案。
Kafka Connector 任務設定:
hadoop.conf.dir=<core-site.xml所在目錄>
store.url=jfs://<JuiceFS檔案系統名稱>/<路徑>
在整個實施過程中陸陸續續踩了一些坑,積累了一些經驗,分享給大家做參考。
阿里雲 EMR 和元件相關
相容性
num_nulls=-1
的改成 num_nulls=0
. 可能需要用到 CatalogObjects.thrift 檔案。Snappy: RawUncompress failed
,可能是 IMPALA-10005 導致的。規避方案是不要對 Textfile 檔案使用 snappy 壓縮。CONCAT_WS
函數行為有差異,老版本 CONCAT_WS('_', 'abc', NULL)
會返回 NULL
,而新版本返回 'abc'
.效能
oss://
和 jfs://
(本意是支援 JindoFS,但 JuiceFS 也預設使用 jfs 這個 scheme)設定獨立的 IO 執行緒數。在 EMR 控制檯上增加或修改 Impala 的設定項 num_oss_io_threads
.運維
/mnt/disk1/log/spark/spark-hadoop-org.apache.spark.sql.hive.thriftserver.HiveThriftServer2-1-emr-header-1.cluster-xxxxxx.out
),導致硬碟寫滿。解決方案有兩個:設定 log rotate 或把 spark.driver.extraJavaOptions
設定清空(阿里雲技術支援的建議)。JuiceFS 相關
如有幫助的話歡迎關注我們專案 Juicedata/JuiceFS 喲! (0ᴗ0✿)