伴魚基於 Flink 構建資料整合平臺的設計與實現

ApacheFlink發表於2021-12-10

資料倉儲有四個基本的特徵:面向主題的、整合的、相對穩定的、反映歷史變化的。其中資料整合是資料倉儲構建的首要前提,指將多個分散的、異構的資料來源整合在一起以便於後續的資料分析。將資料整合過程平臺化,將極大提升資料開發人員的效率。本文主要內容為:

  1. 資料整合 VS 資料同步
  2. 整合需求
  3. 資料整合 V1
  4. 資料整合 V2
  5. 線上效果
  6. 總結

Flink 中文學習網站
https://flink-learning.org.cn

A data warehouse is a subject-oriented, integrated, nonvolatile, and time-variant collection of data in support of management’s decisions.

—— Bill Inmon

一、資料整合 VS 資料同步

「資料整合」往往和「資料同步」在概念上存在一定的混淆,為此我們對這二者進行了區分。

  • 「資料整合」特指面向資料倉儲 ODS 層的資料同步過程,
  • 「資料同步」面向的是一般化的 Source 到 Sink 的資料傳輸過程。

二者的關係如下圖所示:

vs

「資料同步平臺」提供基礎能力,不摻雜具體的業務邏輯。「資料整合平臺」是構建在「資料同步平臺」之上的,除了將原始資料同步之外還包含了一些聚合的邏輯(如通過資料庫的日誌資料對快照資料進行恢復,下文將會詳細展開)以及數倉規範相關的內容(如數倉 ODS 層庫表命名規範)等。目前「資料同步平臺」的建設正在我們的規劃之中,但這並不影響「資料整合平臺」的搭建,一些同步的需求可提前在「實時計算平臺」建立,以「約定」的方式解耦。

值得一提的是「資料整合」也應當涵蓋「資料採集」(由特定的工具支援)和「資料清洗」(由採集粒度、日誌規範等因素決定)兩部分內容,這兩部分內容各個公司都有自己的實現,本文將不做詳細介紹。

二、整合需求

目前伴魚內部資料的整合需求主要體現在三塊:Stat Log (業務標準化日誌或稱統計日誌)、TiDB 及 MongoDB。除此之外還有一些 Service Log、Nginx Log 等,此類不具備代表性不在本文介紹。另外,由於實時數倉正處於建設過程中,目前「資料整合平臺」只涵蓋離線數倉(Hive)。

  • Stat Log:業務落盤的日誌將由 FileBeat 元件收集至 Kafka。由於日誌為 Append Only 型別, 因此 Stat Log 整合相對簡單,只需將 Kafka 資料同步至 Hive 即可。
  • DB(TiDB、MongoDB):DB 資料相對麻煩,核心訴求是數倉中能夠存在業務資料庫的映象,即存在業務資料庫中某一時刻(天級 or 小時級)的資料快照,當然有時也有對資料變更過程的分析需求。因此 DB 資料整合需要將這兩個方面都考慮進去。

由於以上兩種型別的資料整合方式差異較大,下文將分別予以討論。

三、資料整合 V1

伴魚早期「資料整合平臺」已具備雛形,這個階段主要是藉助一系列開源的工具實現。隨著時間推進,這個版本暴露的問題也逐漸增多,接下來將主要從資料流的角度對 V1 進行闡述,更多的細節問題將在 V2 版本的設計中體現。

3.1 Stat Log

日誌的整合並未接入平臺,而是煙囪式的開發方式,資料整合的鏈路如下圖所示:

v1.0_log

Kafka 中的資料先經過 Flume 同步至 HDFS,再由 Spark 任務將資料從 HDFS 匯入至 Hive 並建立分割槽。整體鏈路較長且引入了第三方元件(Flume)增加了運維的成本,另外 Kafka 的原始資料在 HDFS 冗餘儲存也增加了儲存的開銷。

3.2 DB

DB 資料的整合主要是基於查詢的方式(批的方式,通過 Select 查詢進行全表掃描得到快照資料)實現,其鏈路如下圖所示:

v1.0_db

使用者通過平臺提交整合任務,由 Airflow 定時任務掃描整合平臺後設資料庫,生成對應的取數任務(TiDB 的資料通過 Sqoop 工具,MongoDB 的資料則通過 Mongoexport 工具)。可以看到 V1 版本並沒有獲取資料庫的變更的日誌資料,不能滿足對資料變更過程的分析訴求。

由於 Sqoop 任務最終要從 TiDB 生產環境的業務資料庫獲取資料,資料量大的情況下勢必對業務資料庫造成一定的影響。Mongoexport 任務直接作用在 MongoDB 的隱藏節點(無業務資料請求),對於線上業務的影響可以忽略不計。基於此,DBA 單獨搭建了一套 TiDB 大資料叢集,用於將體量較大的業務資料庫同步至此(基於 TiDB Pump 和 Drainer 元件),因此部分 Sqoop 任務可以從此叢集拉群資料以消除對業務資料庫的影響。從資料流的角度,整個過程如下圖所示:

v1.0

是否將生產環境 TiDB 業務資料庫同步至 TiDB 大資料叢集由數倉的需求以及 DBA 對於資料量評估決定。可以看出,這種形式也存在著大量資料的冗餘,叢集的資源隨著同步任務的增加時長達到瓶頸。並且隨著後續的演進,TiDB 大資料叢集也涵蓋一部分資料應用生產環境的業務資料庫,叢集作用域逐漸模糊。

四、資料整合 V2

V2 版本我們引入了 Flink,將同步的鏈路進行了簡化,DB 資料整合從之前的基於查詢的方式改成了基於日誌的方式(流的方式),大大降低了冗餘的儲存。

4.1 Stat Log

藉助 Flink 1.11 版本後對於 Hive Integration 的支援,我們可以輕鬆的將 Kafka 的資料寫入 Hive,因此 Stat Log 的整合也就變得異常簡單(相比 V1 版本,去除了對 Flume 元件的依賴,資料冗餘也消除了),同時 Flink Exactly-Once 的語義也確保了資料的準確性。從資料流的角度,整個過程如下圖所示:

v2.0_log

目前按照小時粒度生成日誌分割槽,幾項 Flink 任務配置引數如下:

checkpoint: 10 min
watermark: 1 min
partition.time-extractor.kind: ‘custom’
sink.partition-commit.delay: ‘3600s’
sink.partition-commit.policy.kind: ‘metastore,success-file’
sink.partition-commit.trigger: ‘partition-time’

4.2 DB

基於日誌的方式對 DB 資料進行整合,意味著需要採集 DB 的日誌資料,在我們目前的實現中 TiDB 基於 Pump 和 Drainer 元件(目前生產環境資料庫叢集版本暫不支援開啟 TICDC),MongoDB 基於 MongoShake 元件,採集的資料將輸送至 Kafka。採用這種方式,一方面降低了業務資料庫的查詢壓力,另一方面可以捕捉資料的變更過程,同時冗餘的資料儲存也消除了。不過由於原始資料是日誌資料,需要通過一定的手段還原出快照資料。新的鏈路如下圖所示:

v2.0_db

使用者提交整合任務後將同步建立三個任務:

  • 增量任務(流):「增量任務」將 DB 日誌資料由 Kafka 同步至 Hive。由於採集元件都是按照叢集粒度進行採集,且叢集數量有限,目前都是手動的方式將同步的任務在「實時計算平臺」建立,整合任務建立時預設假定同步任務已經 ready,待「資料同步平臺」落地後可以同步做更多的自動化操作和校驗。
  • 存量任務(批):要想還原出快照資料則至少需要一份初始的快照資料,因此「存量任務」的目的是從業務資料庫拉取整合時資料的初始快照資料。
  • Merge 任務(批):「Merge 任務」將存量資料和增量資料進行聚合以還原快照資料。還原後的快照資料可作為下一日的存量,因此「存量任務」只需排程執行一次,獲取初始快照資料即可。

「存量任務」和「Merge 任務」由離線排程平臺 Dolphinscheduler(簡稱 DS)排程執行,任務執行過程中將從整合任務的後設資料庫中獲取所需的資訊。目前「Merge 任務」按小時粒度排程,即每小時還原快照資料。

從資料流的角度,整個過程如下圖所示:

v2.0

DB 的資料整合相較於 Stat Log 複雜性高,接下來以 TiDB 的資料整合為例講述設計過程中的一些要點(MongoDB 流程類似,區別在於存量同步工具及資料解析)。

4.2.1 需求表達

對於使用者而言,整合任務需要提供以下兩類資訊:

  • TiDB 源資訊:包括叢集、庫、表
  • 整合方式:整合方式表示的是快照資料的聚合粒度,包括全量和增量。全量表示需要將存量的快照資料與今日的增量日誌資料聚合,而增量表示只需要將今日的增量日誌資料聚合(即便增量方式無需和存量的快照資料聚合,但初始存量的獲取依舊是有必要的,具體的使用形式由數倉人員自行決定)。

4.2.2 存量任務

存量任務雖然有且僅執行一次,但為了完全消除資料整合對業務資料庫的影響,我們選擇資料庫的備份-恢復機制來實現。公司內部資料庫的備份和恢復操作已經平臺化,叢集將定期進行備份(天粒度),通過平臺可以查詢到叢集的最新備份,並且可由介面觸發備份恢復操作,故存量的獲取可直接作用於恢復的資料庫。

由於資料庫備份的時間點與整合任務提交的時間點並不一定是同一天,這之間存在著一定的時間差將導致存量快照資料不符合我們的預期,各時間點的關係如下圖所示:

time

按照我們的設定,存量快照資料應當是包含 T4 之前的全部資料,而實際備份的快照資料僅包含 T1 之前的全部資料,這之間存在這 N 天的資料差。

注:這裡之所以不說資料差集為 T1 至 T4 區間的資料,是因為增量的 Binlog 資料是以整點為分割槽的,在 Merge 的時候也是將整點的分割槽資料與存量資料進行聚合,並支援了資料去重。因此 T1 時刻的存量資料與 T0-T3 之間的增量資料的 Merge 結果等效於 T0 時刻的存量資料與 T0-T3 之間的增量資料的 Merge 結果。所以 T1 至 T4 的資料差集等效 T0 至 T3 的資料差集,即圖示中的 N 天資料。

對於缺失的這部分資料實則是可以在「存量任務」中進行補全,仔細分析這其實是可以通過執行的 「Merge 任務」的補數操作實現。

整個「存量任務」的工作流如下圖所示:

stock

  • 同步觸發資料庫平臺進行備份恢復,產生回執 ID。
  • 通過回執 ID 輪訓備份恢復狀態,恢復失敗需要 DBA 定位異常,故將下線整個工作流,待恢復成功可在平臺重新恢復執行「存量任務」。恢復進行中,工作流直接退出,藉助 DS 定時排程等待下次喚醒。恢復成功,進入後續邏輯。
  • 從恢復庫中拉取存量,判定存量是否存在資料差,若存在則執行 Merge 任務的補數操作,整個操作可冪等執行,如若失敗退出此次工作流,等待下次排程。
  • 成功,下線整個工作流,任務完成。

4.2.3 Merge 任務

Merge 任務的前提是存量資料與增量資料都已經 ready,我們通過 _SUCCESS 檔案進行標記。整個「Merge 任務」的工作流如下圖所示:

merge

  • 校驗檔案標記是否存在,若不存在說明資料未 ready ,進行報警並退出工作流等待下次排程。
  • 執行 Merge 操作,失敗報警並退出工作流等待下次排程。
  • 成功,退出工作流等待下次排程。

Merge 操作通過 Flink DataSet API 實現。核心邏輯如下:

  • 載入存量、增量資料,統一資料格式(核心欄位:主鍵 Key 作為同一條資料的聚合欄位;CommitTs 標識 binlog 的提交時間,存量資料預設為 0 早於增量資料;OpType 標識資料操作型別,包括:Insert、Update、Delete,存量資料預設為 Insert 型別),將兩份資料進行 union。
  • 按照主鍵聚合。
  • 保留聚合後 CommitTs 最大的資料條目,其餘丟棄。
  • 過濾 OpType 為 Delete 型別的資料條目。
  • 輸出聚合結果。

核心程式碼:

allMergedData.groupBy(x -> x.getKeyCols())
             .reduce(new ReduceFunction<MergeTransform>() {
                 
                 public MergeTransform reduce(MergeTransform value1, MergeTransform value2) throws Exception {
                     if (value1.getCommitTS() > value2.getCommitTS()){
                         return value1;
                     }
                     return value2;
                 }
             })
             .filter(new FilterFunction<MergeTransform>() { //增量:過濾掉 op=delete
                 
                 public boolean filter(MergeTransform merge) throws Exception {
                     if (merge.getOpType().equals(OPType.DELETE)){
                         return false;
                     }
                     return true;
                 }
             })
             .map(x -> x.getHiveColsText())
             .writeAsText(outPath);

主要思想為「後來者居上」,針對於 Insert、Update 操作,最新值直接覆蓋舊值,針對 Delete 操作,直接丟棄。這種方式也天然的實現了資料去重操作。

4.2.4 容錯性與資料一致性保證

我們大體可以從三個任務故障場景下的處理方式來驗證方案的容錯性。

  • 「存量任務」異常失敗:通常是備份恢復失敗導致,DS 任務將傳送失敗報警,因「資料庫平臺」暫不支援恢復重試,需人工介入處理。同時「Merge 任務」檢測不到存量的 _SUCCESS 標記,工作流不會向後推進。
  • 「增量任務」異常失敗:Flink 自身的容錯機制以及「實時計算平臺」的外部檢測機制保障「增量任務」的容錯性。若在「Merge 任務」排程執行期間「增量任務」尚未恢復,將誤以為該小時無增量資料跳過執行,此時相當於快照更新延遲(Merge 是將全天的增量資料與存量聚合,在之後的排程時間點如果「增量任務」恢復又可以聚合得到最新的快照),或者在「增量任務」恢復後可人為觸發「Merge 任務」補數。
  • 「Merge 任務」異常失敗:任務具有冪等性,通過設定 DS 任務失敗後的重試機制保障容錯性,同時傳送失敗報警。

以上,通過自動恢復機制和報警機制確保了整個工作流的正確執行。接下來我們可以從資料的角度看一下方案對於一致性的保障。

資料的一致性體現在 Merge 操作。兩份資料聚合,從程式碼層面一定可以確保演算法的正確性(這是可驗證的、可測試的),那麼唯一可能導致資料不一致的情況出現在兩份輸入的資料上,即存量和增量,存在兩種情況:

  • 存量和增量資料有交疊:體現在初始存量與整點的增量資料聚合場景,由於演算法天然的去重性可以保證資料的一致。
  • 存量和增量資料有缺失:體現在增量資料的缺失上,而增量資料是由 Flink 將 Kafka 資料寫入 Hive 的,這個過程中是有一定的可能性造成資料的不一致,即分割槽提交後的亂序資料。雖然說亂序資料到來後的下一次 checkpoint 時間點分割槽將再次提交,但下游任務一般是檢測到首次分割槽提交就會觸發執行,造成下游任務的資料不一致。

針對 Flink 流式寫 Hive 過程中的亂序資料處理可以採取兩種手段:一是 Kafka 設定單分割槽,多分割槽是產生導致亂序的根因,通過避免多分割槽消除資料亂序。二是報警補償,亂序一旦產生流式任務是無法完全避免的(可通過 watermark 設定亂序容忍時間,但終有一個界限),那麼只能通過報警做事後補償。問題轉換成了如何感知到亂序,我們可以進一步分析,既然亂序資料會觸發前一個分割槽的二次提交,那麼只需要在提交分割槽的時候檢測前一個分割槽是否存在 _SUCCESS 標記便可以知曉是否是亂序資料以及觸發報警。

五、線上效果

總覽

home

存量任務

ds_stock

Merge 任務

ds_merge

六、總結

本文闡述了伴魚「資料整合平臺」核心設計思路,整個方案還有一些細節未在文章中體現,如資料 Schema 的變更、DB 日誌資料的解析等,這些細節對於平臺構建也至關重要。目前伴魚絕大部分的整合任務已切換至新的方式並穩定執行。我們也正在推進實時數倉整合任務的接入,以提供更統一的體驗。

原文:伴魚資料整合平臺的設計與實現

近期熱點

img


更多 Flink 相關技術問題,可掃碼加入社群釘釘交流群
第一時間獲取最新技術文章和社群動態,請關注公眾號~

image.png

相關文章