Apache Hudi 在 B 站構建實時資料湖的實踐

ApacheFlink發表於2021-09-27

本文作者喻兆靖,介紹了為什麼 B 站選擇 Flink + Hudi 的資料湖技術方案,以及針對其做出的優化。主要內容為:

  1. 傳統離線數倉痛點
  2. 資料湖技術方案
  3. Hudi 任務穩定性保障
  4. 資料入湖實踐
  5. 增量資料湖平臺收益
  6. 社群貢獻
  7. 未來的發展與思考

一、傳統離線數倉痛點

1. 痛點

之前 B 站數倉的入倉流程大致如下所示:

img

在這種架構下產生了以下幾個核心痛點:

  1. 大規模的資料落地 HDFS 後,只能在凌晨分割槽歸檔後才能查詢並做下一步處理;
  2. 資料量較大的 RDS 資料同步,需要在凌晨分割槽歸檔後才能處理,並且需要做排序、去重以及 join 前一天分割槽的資料,才能產生出當天的資料;
  3. 僅能通過分割槽粒度讀取資料,在分流等場景下會出現大量的冗餘 IO。

總結一下就是:

  • 排程啟動晚;
  • 合併速度慢;
  • 重複讀取多。

2. 痛點思考

  • 排程啟動晚

    思路:既然 Flink 落 ODS 是準實時寫入的,有明確的檔案增量概念,可以使用基於檔案的增量同 步,將清洗、補維、分流等邏輯通過增量的方式進行處理,這樣就可以在 ODS 分割槽未歸檔的時 候就處理資料,理論上資料的延遲只取決於最後一批檔案的處理時間。

  • 合併速度慢

    思路:既然讀取已經可以做到增量化了,那麼合併也可以做到增量化,可以通過資料湖的能力結 合增量讀取完成合並的增量化。

  • 重複讀取多

    思路:重複讀取多的主要原因是分割槽的粒度太粗了,只能精確到小時/天級別。我們需要嘗試一 些更加細粒度的資料組織方案,將 Data Skipping 可以做到欄位級別,這樣就可以進行高效的數 據查詢了。

3. 解決方案: Magneto - 基於 Hudi 的增量資料湖平臺

以下是基於 Magneto 構建的入倉流程:

img

  • Flow

    • 使用流式 Flow 的方式,統一離線和實時的 ETL Pipline
  • Organizer

    • 資料重組織,加速查詢
    • 支援增量資料的 compaction
  • Engine

    • 計算層使用 Flink,儲存層使用 Hudi
  • Metadata

    • 提煉表計算 SQL 邏輯
    • 標準化 Table Format 計算正規化

二、資料湖技術方案

1. Iceberg 與 Hudi 的取捨

1.1 技術細節對比

img

1.2 社群活躍度對比

統計截止至 2021-08-09

img

1.3 總結

大致可以分為以下幾個主要緯度來進行對比:

  • 對 Append 的支援

    Iceberg 設計之初的主要支援方案,針對該場景做了很多優化。 Hudi 在 0.9 版本中對 Appned 模式進行了支援,目前在大部分場景下和 Iceberg 的差距不大, 目前的 0.10 版本中仍然在持續優化,與 Iceberg 的效能已經非常相近了。

  • 對 Upsert 的支援

    Hudi 設計之初的主要支援方案,相對於 Iceberg 的設計,效能和檔案數量上有非常明顯的優 勢,並且 Compaction 流程和邏輯全部都是高度抽象的介面。 Iceberg 對於 Upsert 的支援啟動較晚,社群方案在效能、小檔案等地方與 Hudi 還有比較明顯 的差距。

  • 社群活躍度

    Hudi 的社群相較於 Iceberg 社群明顯更加活躍,得益於社群活躍,Hudi 對於功能的豐富程度與 Iceberg 拉開了一定的差距。

綜合對比,我們選擇了 Hudi 作為我們的資料湖元件,並在其上繼續優化我們需要的功能 ( Flink 更好的整合、Clustering 支援等)

2. 選擇 Flink + Hudi 作為寫入方式

我們選擇 Flink + Hudi 的方式整合 Hudi 的主要原因有三個:

  1. 我們部分自己維護了 Flink 引擎,支撐了全公司的實時計算,從成本上考慮不想同時維護兩套計算引擎,尤其是在我們內部 Spark 版本也做了很多內部修改的情況下。
  2. Spark + Hudi 的整合方案主要有兩種 Index 方案可供選擇,但是都有劣勢:

    • Bloom Index:使用 Bloom Index 的話,Spark 會在寫入的時候,每個 task 都去 list 一遍所有的檔案,讀取 footer 內寫入的 Bloom 過濾資料,這樣會對我們內部壓力已經非常大的 HDFS 造成非常恐怖的壓力。
    • Hbase Index:這種方式倒是可以做到 O(1) 的找到索引,但是需要引入外部依賴,這樣會使整個方案變的比較重。
  3. 我們需要和 Flink 增量處理的框架進行對接。

3. Flink + Hudi 整合的優化

3.1 Hudi 0.8 版本整合 Flink 方案

img

針對 Hudi 0.8 版本整合暴露出來的問題,B站和社群合作進行了優化與完善。

3.2 Bootstrap State 冷啟動

背景:支援在已經存在 Hudi 表啟動 Flink 任務寫入,從而可以做到由 Spark on Hudi 到 Flink on Hudi 的方案切換

原方案:

img

問題:每個 Task 處理全量資料,然後選擇屬於當前 Task 的 HoodieKey 存入 state 優化方案。

img

  • 每個 Bootstrap Operator 在初始化時,載入屬於當前 Task 的 fileId 相關的 BaseFile 和 logFile;
  • 將 BaseFile 和 logFile 中的 recordKey 組裝成 HoodieKey,通過 Key By 的形式傳送給 BucketAssignFunction,然後將 HoodieKey 作為索引儲存在 BucketAssignFunction 的 state 中。

效果:通過將 Bootstrap 功能單獨抽出一個 Operator,做到了索引載入的可擴充套件性,載入速度提升 N (取決於併發度) 倍。

3.3 Checkpoint 一致性優化

背景:在 Hudi 0.8 版本的 StreamWriteFunction 中,存在極端情況下的資料一致性問題。

原方案:

img

問題:CheckpointComplete不在CK生命週期內,存在CK成功但是instant沒有commit的情 況,從而導致出現資料丟失。

優化方案:

img

3.4 Append 模式支援及優化

背景:Append 模式是用於支援不需要 update 的資料集時使用的模式,可以在流程中省略索引、 合併等不必要的處理,從而大幅提高寫入效率。

img

主要修改:

  • 支援每次 FlushBucket 寫入一個新的檔案,避免出現讀寫的放大;
  • 新增引數,支援關閉 BoundedInMemeoryQueue 內部的限速機制,在 Flink Append 模式下只需要將 Queue 的大小和 Bucket buffer 設定成同樣的大小就可以了;
  • 針對每個 CK 產生的小檔案,制定自定義 Compaction 計劃;
  • 通過以上的開發和優化之後,在純 Insert 場景下效能可達原先 COW 的 5 倍。

三、Hudi 任務穩定性保障

1. Hudi 整合 Flink Metrics

通過在關鍵節點上報 Metric,可以比較清晰的掌握整個任務的執行情況:

img

img

2. 系統內資料校驗

img

3. 系統外資料校驗

img

四、資料入湖實踐

1. CDC資料入湖

1.1 TiDB入湖方案

由於目前開源的各種方案都沒辦法直接支援 TiDB 的資料匯出,直接使用 Select 的方式會影響數 據庫的穩定性,所以拆成了全量 + 增量的方式:

  1. 啟動 TI-CDC,將 TIDB 的 CDC 資料寫入對應的 Kafka topic;
  2. 利用 TiDB 提供的 Dumpling 元件,修改部分原始碼,支援直接寫入 HDFS;
  3. 啟動 Flink 將全量資料通過 Bulk Insert 的方式寫入 Hudi;
  4. 消費增量的 CDC 資料,通過 Flink MOR 的方式寫入 Hudi。

1.2 MySQL 入湖方案

MySQL 的入湖方案是直接使用開源的 Flink-CDC,將全量和增量資料通過一個 Flink 任務寫入 Kafka topic:

  1. 啟動 Flink-CDC 任務將全量資料以及 CDC 資料匯入 Kafka topic;
  2. 啟動 Flink Batch 任務讀取全量資料,通過 Bulk Insert 寫入 Hudi;
  3. 切換為 Flink Streaming 任務將增量 CDC 資料通過 MOR 的方式寫入 Hudi。

img

2. 日誌資料增量入湖

  • 實現 HDFSStreamingSource 和 ReaderOperator,增量同步 ODS 的資料檔案,並且通過寫入 ODS 的分割槽索引資訊,減少對 HDFS 的 list 請求;
  • 支援 transform SQL 配置化,允許使用者進行自定義邏輯轉化,包括但不限於維表 join、自定義 udf、按欄位分流等;
  • 實現 Flink on Hudi 的 Append 模式,大幅提升不需要合併的資料寫入速率。

img

五、增量資料湖平臺收益

  • 通過 Flink 增量同步大幅度提升了資料同步的時效性,分割槽就緒時間從 2:00~5:00 提前到 00:30 分內;
  • 儲存引擎使用 Hudi,提供使用者基於 COW、MOR 的多種查詢方式,讓不同使用者可以根據自己 的應用場景選擇合適的查詢方式,而不是單純的只能等待分割槽歸檔後查詢;
  • 相較於之前數倉的 T+1 Binlog 合併方式,基於 Hudi 的自動 Compaction 使得使用者可以將 Hive 當成 MySQL 的快照進行查詢;
  • 大幅節約資源,原先需要重複查詢的分流任務只需要執行一次,節約大約 18000 core。

六、社群貢獻

上述優化都已經合併到 Hudi 社群,B站在未來會進一步加強 Hudi 的建設,與社群一起成⻓。

部分核心PR

https://issues.apache.org/jir...

https://issues.apache.org/jir...

https://issues.apache.org/jir...

https://issues.apache.org/jir...

https://issues.apache.org/jir...

https://issues.apache.org/jir...

https://issues.apache.org/jir...

七、未來的發展與思考

  • 平臺支援流批一體,統一實時與離線邏輯;
  • 推進數倉增量化,達成 Hudi ODS -> Flink -> Hudi DW -> Flink -> Hudi ADS 的全流程;
  • 在 Flink 上支援 Hudi 的 Clustering,體現出 Hudi 在資料組織上的優勢,並探索 Z-Order 等加速多維查詢的效能表現;
  • 支援 inline clustering。

相關文章